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?
|Style|| assert-style syntax||spec-style syntax|
|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
Initialize bundler with:
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
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
Now, as an experiment, rename the test method
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
*Soon we’ll see that there are other base classes we will descend from, but under the hood, they all descend from the superclass
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
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
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 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
- The file name of each of our test files must end with
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