Minitest is a powerful testing framework for testing Ruby and Ruby on Rails apps. It has a clear, expressive style and is a more elite tool than its sibling testing library, Rspec.
What are the differences between Minitest and Rspec?
Minitest | Rspec | |
Style | assert-style syntaxassert_equal(expected, thing_to_test) | spec-style syntaxexpect(thing_to_test).to be(expected) |
Ease of Use | Simple to use | Simple to use |
Writing Style | Prefers Ruby-ish style conventions for code re-use — use inheritance, modules, helpers, etc. | Prefers Rspec’s DSL for code re-use, makes heavy use of nested context blocks |
Getting Started | Many extensions make it confusing to get started or to understand which extension is right for which purpose. | Most core functionality is built-in with a few extensions for mocking. |
Speed to run | Very fast | Slower because matches create objects that need to be garbage collected |
Code vs. Magic | More code and less magic | More magic and less code |
Customer matchers | Encouraged | Not encouraged |
Once you start with one, you will generally stay with that choice for the life of the app, so it is important to understand up-front the choices you are making.
Unfortunately, Stack Overflow is filled with either bad or useless advice about setting up Minitest, and the nuances between the different gems are not carefully explained in most of the Stack Overflow posts you will find. Most apps are set up by a single architect or founding engineer. The engineers who join the team just copy what they already see in the codebase, without gaining a solid understanding of the fundamentals of the various Minitest options available.
This guide will introduce you to minitest, minitest-spec, minitest-rails, and minitest-rails-capybara and show you the specific syntax differences between the different flavors of Minitest.
For demonstration purposes we’ll start with a bare-bones Ruby app. Then we’ll throw that away and do it for a brand new Rails app.
1-1/ Using Minitest Outside of Rails
Make a new folder called
mkdir MinitestBasic
cd MinitestBasic
Initialize bundler with:
bundle init
Your project now contains only two files:
The Gemfile has simply this content. (Bunder actually adds a commented-out Gem for “rails” — I’m not sure why— I’ve removed it.)
# frozen_string_literal: true
source "https://rubygems.org"
gem "minitest", "~> 5.18"
Now create a folder called test/
and a file called the_truth.rb
Remember, we’re just doing this for demonstration purposes.
# test/the_truth.rb
require 'minitest/autorun'
class TheTruth < Minitest::Test
def test_the_truth
assert_equal(2, 1+1)
end
end
Here, we are simply asserting that 1+1 = 2. Notice that the assert takes two arguments (actually three, which we’ll introduce later): First, the expected value, and second, the thing you are testing.
Although it might seem to read backward, this is the standard format:
In Minitest, the expected result comes first, followed by the code you are testing (or the actual result).
Run your test using ruby
test/the_truth.rb
Now, as an experiment, rename the test method test_the_truth
to one_plus_one_equals_two
class TheTruth < Minitest::Test def one_plus_one_equals_two assert_equal(2, 1+1) end end
If you run ruby test/the_truth.rb
, you’ll see no examples are run. That’s because we renamed our test and removed the word “test_
” from the start of it.
% ruby test/the_truth.rb
Run options: --seed 17451
# Running:
Finished in 0.000288s, 0.0000 runs/s, 0.0000 assertions/s.
0 runs, 0 assertions, 0 failures, 0 errors, 0 skips
This demonstrates that by default, for your test to be picked up by Minitest, you’ll need two key elements:
- The test must be a subclass of Minitest::Test *
- The method name of the test must begin with
test_
*Soon we’ll see that there are other base classes we will descend from, but under the hood, they all descend from the superclass Minitest::Test
.
Before continuing, change your test back to using the full method name test_the_truth
so it will run again:
def test_one_plus_one_equals_two assert_equal(2, 1+1) end
1-2/ Using a Rakefile
Notice that in the above example, we used a test file called test/the_truth.rb
. We covered the requirement your test method names must begin with test_
, but we didn’t talk about your filename. That’s because we ran our test explicitly using ruby test/the_truth.rb
In most Ruby projects, you will also want your filenames to end with _test.rb
, but that requirement is imposed by the Rakefile setup we’re about to do. Using a Rakefile, we can setup a default rake task to run all of the tests in our test/
folder
Make a new file Rakefile (with a capital R
and no extension), like so:
require "rake/testtask"
Rake::TestTask.new do |t|
t.libs << "test"
t.pattern = "test/**/*.rb"
end
task :default => :test
Here, we’re creating a new Rake task that will run as default. By “running as default” we mean that unlike other Rake tasks, where you have to specify the name of the task (such as rake xyz
), we only need to type rake to run our tests. (That’s what the :default => :test part does).
We are telling the Rake task the test folder is where to look and that we should run any file that matches the pattern test/**/*.rb
(so basically any Ruby file inside the test/
directory or its subdirectories).
Indeed, if we run just rake
now on the command prompt, we’ll run our specs:
% rake
Run options: --seed 63079
# Running:
.
Finished in 0.000344s, 2906.9861 runs/s, 2906.9861 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
A typical Rake setup does not run every Ruby file inside of the test folder, because that folder often contains test support files that we don’t want to consider test runs.
To get around this problem, let’s make three small changes:
1) change the line t.pattern = "test/**/*.rb"
to t.pattern = "test/**/*_test.rb"
2) rename the file itself from the_truth.rb to the_truth_test.rb.
3) Also rename the class from TheTruth to TheTruthTest to match the filename:
test/the_truth_test.rb
# test/the_truth_test.rb require 'minitest/autorun' class TheTruthTest < Minitest::Test def test_one_plus_one_equals_two assert_equal(2, 1+1) end end
Because of the Rakefile, we now have 3 rules to remember for what determines which test will run:
- The test must be a subclass of Minitest::Test
- The method name of each test must begin with
test_
- The file name of each of our test files must end with
_test.rb
Keep these 3 rules in mind moving forward. This is especially important when you write a test that Minitest doesn’t seem to run because it probably means you forgot one of the three items above.
Rake task modifiers
MT_LIB_EXTRAS :: Extra libs to dynamically override/inject for custom runs. N :: -n: Tests to run (string or /regexp/). X :: -x: Tests to exclude (string or /regexp/). A :: Any extra arguments. Honors shell quoting. MT_CPU :: How many threads to use for parallel test runs SEED :: -s --seed Sets random seed. TESTOPTS :: Deprecated, same as A FILTER :: Deprecated, same as A
1-3/ Minitest Spec-Style
Minitest support a spec-style syntax using describe and it blocks instead of classes. Consider the class-style syntax above:
class TheTruthTest < Minitest::Test
def test_one_plus_one_equals_two
assert_equal(2, 1+1)
end
end
Let’s rewrite it using spec-style: Change the class itself to a describe block, putting the class name into quotation marks and removing the base class. Then change def test_one_plus_one_equals_two
to it "should add one plus one to make two"
describe "TheTruthTest" do it "one plus one equals two" do assert_equal(2, 1+1) end end
Under the hood, Minitest converts this into what you see above, making this functionally equivalent.
You cannot mix & match syntax in the same file. To use the it
style blocks, they must be in describe
blocks.