Testing Rails with MiniTest
This is a post that wrote a while ago and that I rescue from the original blog which is not available anymore.
MiniTest is a library for TDD, BDD, mocking and benchmarking, and it has been around for a while now.
In the world of Ruby, Test::Unit and RSpec have been the most popular testing libraries, Test::Unit was bundled as standard library on Ruby, but this has changed with the release of Ruby 1.9 and 2.0, where Test::Unit was replaced with MiniTest.
This replacement has been transparent for whoever uses Test::Unit, because MiniTest offers a layer of compatibility with the first, so if you are still using require ‘test/unit’, under the hood you are really using MiniTest::Unit.
For people who like better the spec syntax provided by RSpec, MiniTest also provides a MiniTest::Spec which have a syntax like, but with some limitations.
Why use MiniTest?
As I explained before, it comes with the newer Ruby versions out of the box, so it’s available right away, no need to install anything else.
MiniTest has been designed to be a small and fast testing framework, being compatible with Test::Unit makes it the logical choice.
If you are a hardcore RSpec user, maybe you would not use it for existing projects, but if you are starting a new project, having an option for a fast test framework, which is compatible with the describe/it/let syntax could be appealing.
If you want to know more about MiniTest visit Matt Sears MiniTest Quick Reference or MiniTest documentation.
Testing Rails
If you are using Rails 3.x then you need some configuration in order to use MiniTest. If you are on Rails 4.0, you get by default MiniTest with MiniTest::Unit, this is because of the Rails dependency on Ruby 1.9/2.0.
Setup
To test Rails we are going to get some help from the gems minitest-rails and minitest-rails-capybara, both from blowmage, we should start with the Gemfile test group:
group :test do
gem 'minitest-rails'
gem 'minitest-rails-capybara'
gem 'minitest-colorize'
gem 'minitest-focus'
end
I have added two more gems, the first one will colorize the output of the test runner and the second one will allow us to run just tests that have a focus decorator, since MiniTest does not support tags as RSpec does.
On a brand new Rails app, MiniTest will reuse current test_helper.rb and the whole test directory structure.
If you still want to regenerate the test_helper.rb just run the following generator.
rails g mini_test:install
I personally prefer to start by modifying the Rails generated test_helper.rb and just add what I need, so let’s follow that way.
At first we should require in our test_helper.rb all MiniTest gems that we added to our Gemfile. After line require “rails/test_help” add the following lines:
require 'minitest/rails'
require 'minitest/rails/capybara'
require 'minitest/focus'
require 'minitest/colorize'
In our test_helper.rb ActiveSupport:TestCase class is redefined and ready for some customization, this is the base class for all test classes, here we can define setup and teardown methods, which will be called before and after each test run.
class ActiveSupport::TestCase
fixtures :all def self.prepare
# Add code that needs to be executed before test suite start
end
prepare def setup
# Add code that need to be executed before each test
end def teardown
# Add code that need to be executed after each test
end
end
Rails 3
WIth this version we would also need to tell our application.rb to not load test_unit but MiniTest, so we need to locate the line:
require 'rails/all'
Comment it out and explicitly load Rails as follows, with the addition of MiniTest:
require "active_record/railtie"
require "action_controller/railtie"
require 'rake/testtask'
require "action_mailer/railtie"
require "active_resource/railtie"
require "sprockets/railtie"
require "minitest/rails/railtie"
Rails 4
For Rails 4 we do the same as for Rails 3, but we leave out the line that requires active_resource/railitie unless we have included the gem for it and our project requires it.
Execute our tests
To execute our tests in Rails, first we need to know a few differences between Rails 3 and Rails 4.
Rails 3
if we execute rake -T we will see the following tasks:
- rake minitest # Run default tests
- rake minitest:all # Run all tests
- rake minitest:all:quick # Run all tests, ungrouped for quicker execution
- rake minitest:functional # Runs tests under test/functional
- rake minitest:helpers # Runs tests under test/helpers
- rake minitest:performance # Runs tests under test/performance
- rake minitest:unit # Runs tests under test/unit
- rake test # Run default tests
MiniTest will inspect the test folder and create a dynamic task for each folder there, but it will not add that folder to the case when we run the rake minitest task. MiniTest will only run in rake minitest all tests in the following folders: models, helpers, controllers, mailers and integration.
Rails 3 uses functional and unit folders for controllers and models tests, this difference between MiniTest and Rails 3 lies in the fact that MiniTest follows Rails 4 standards. Even MiniTest generators create files inside controllers and models folders, ignoring completely functional and unit folders.
To avoid issues with this behavior, just drop functional and unit folders on Rails 3. If you still want to use functional and unit folder, keep in mind that MiniTest generators will not place test files in the right place and that you will need to move them. Also you will need to add a rake file when you tell MiniTest to run tests on functional and unit folders when you do a:
rake minitest or rake minitest:all
For this change create a new minitest.rake file at lib/task and add the following:
MiniTest::Rails::Testing.default_tasks = %w(functional unit helpers mailers)
Rails 4
if we execute rake -T we will see the following tasks:
- rake minitest # Run default tests
- rake minitest:all # Run all tests
- rake minitest:all:quick # Run all tests, ungrouped for quicker execution
- rake minitest:controllers # Runs tests under test/controllers
- rake minitest:helpers # Runs tests under test/helpers
- rake minitest:models # Runs tests under test/models
- rake test # Run default tests
If we have any additional folder inside test, MiniTest will add a rake task for that folder. MiniTest just works without any other changes with Rails 4.
test_unit like syntax
As I said before, MiniTest uses the test_unit syntax by default, so you can still use Rails default generators for your test files or you can plugin MiniTest generators. If you want to use the latter, we can start adding a new initializer file called generators.rb, in there we tell Rails that we want to use a different testing framework:
Rails.application.config.generators do |g|
g.test_framework :mini_test
end
A side note: In this generators.rb file I usually stop Rails from generating asset files like sass and js for every controller and also stop Rails from creating empty helper files, I mute Rails as follow:
Rails.application.config.generators do |g|
g.helper false
g.assets false
g.view_specs false
end
With this in place we start writing tests for our models like:
class NewsItemTest < ActiveSupport::TestCase
before do
@news_item ||= NewsItem.new
end test 'is valid' do
assert_equal true, @news_item.valid?
end
end
A sample test for a controller would look like:
class NewsControllerTest < ActionController::TestCase
test 'get index' do
get :index assert_response :success
assert_template :index
assert_not_nil assigns(:news)
end
end
Spec-like syntax
MiniTest also includes a Spec syntax, which is very close to the Rspec syntax. To switch Rails generators to use this alternate syntax, we should add an additional option:
Rails.application.config.generators do |g|
g.test_framework :mini_test, spec: true
end
Now, if we generate a test for a model our syntax will look like:
describe NewsItem do
let(:news_item) { NewsItem.new } it 'is valid' do
news_item.valid?.must_equal true
end
end
For a controller, first we need to customize MiniTest assertions a bit, this is because Spec assertions look like the assertions for unit_test, but it is quite simple to change them to be more “Specish”.
Within the module MiniTest::Expectations we can call infect_an_assertion method, which is a way to define a DSL with MiniTest, in this case infect_an_assertion is used to define a method that will be delegated to another one.
In order to perform this, let’s open our test_helper.rb file and add a module definition like:
module MiniTest::Expectations
infect_an_assertion :assert_redirected_to, :must_redirect_to
infect_an_assertion :assert_template, :must_render_template
infect_an_assertion :assert_response, :must_respond_with
end
This will map assert like expectation to must expectation, then we can write our controller tests like:
describe NewsController do
it 'get index' do
get :index must_response_with :success
must_render_template :index
assigns(:news).wont_be_nil
end
end
It’s important to notice that Spec syntax does not have contexts as Rspec does, but this is very simple to add, we just need to open again our test_helper.rb file and add the following alias our ActiveSupport::TestCase class redefinition:
class ActiveSupport::TestCase
# … code that we already have here class << self
alias :context :describe
end
end
With this simple change we can have Specs like:
describe '…' do
context '…' do
it '…' do; end
end
end
Capybara
Earlier we added minitest-rails-capybara into our setup, now is the time to use it. minitest-rails-capybara has a generator for features files, as we saw before we can have features in test_unit syntax or spec syntax. This generator will place our test file into a features directory.
$ rails g mini_test:feature NavigateToHome
If you are using test_unit like syntax, then your feature test would look like:
class NavigateToHome < Capybara::Rails::TestCase
def test_we_are_in_home
visit root_path
assert page.has_content? 'Welcome!'
end
end
in case you are using spec syntax, then test will look like:
feature 'Navigate To Home Feature Test' do
scenario 'we are in home' do
visit root_path
assert page.has_content? 'Welcome!'
end
end
The latest version of minitest-rails-capybara includes a set of matchers — From minitest-matchers gem — that made our test feel more spec-like:
feature 'Navigate To Home Feature Test' do
scenario 'we are in home' do
visit root_path
page.must_have_content 'Welcome!'
end
end
If we would like to have code that is executed before and after each scenario, we can open our test_helper file and add a definition for Capybara::Rails::TestCase which is the base class for our scenarios:
class Capybara::Rails::TestCase
def setup
# Code to be executed before each scenario
end def teardown
# Code to be executed after each scenario
end
end
A good practice would be to add Capybara.reset_sessions! in the tear-down method, just to be sure to reset the Capybara session after each scenario.
Javascript
If you want to run Capybara tests that exercise Javascript, then at the beginning of the scenario you should set the javascript driver like this:
feature 'Navigate To Home Feature Test' do
scenario 'we are in home' do
Capybara.current_driver = Capybara.javascript_driver visit root_path
page.must_have_content 'Welcome!'
end
end
And reset the driver in the tear-down method at the Capybara::Rails::TestCase
def teardown
Capybara.current_driver = Capybara.default_driver
end
If you don’t quite like this solution, then you could include the minitest-metadata gem which will let you set the Javascript driver like:
feature 'Navigate To Home Feature Test' do
scenario 'we are in home', js: true do
visit root_path
page.must_have_content 'Welcome!'
end
end
Run features test
From the section Execute we realize that the only way to run features tests is with rake MiniTest:all and nothing else, rake testand rake minitest will not do it.
This is because we need to register features to be part of the default rake test tasks. We need to modify minitest.rake if you have one, or Rails Rakefile to set MiniTest’s default_tasks to run features.
MiniTest::Rails::Testing.default_tasks << "features"
After this, features tests will run with rake test or rake minitest also.
Conclusions
So far I have been using MiniTest with a few Rails 4 projects with success, I haven’t missed Rspec at all. minitest-rails and minitest-rails-capybara are still a moving target, but lately they have been a bit more stable, they still show some weird behavior with the generators, but you aren’t using generators after all, right? :)
If you are starting a new Rails 4 project, MiniTest should be your first option for your test framework, after all without doing any changes Rails 4 is using it with the test_unit syntax, if you want the spec syntax, and I’m sure that most of you prefer that one, the changes are fairly simple to get started.
Thanks for reading!