Minitest Setup for Rails
I've used RSpec for testing for two years, and I feel like it's time to try something new. Minitest is another popular testing framework for Rails. After several days of trying in a new proejct, I found it very different and efficient in some ways.
In short, it takes much more time to setup and make it work. But once it's done, the testing code is much cleaner than RSpec, which is the best part I feel from Minitest.
Here is how I set up Minitest for a Rails project.
Installation
Add in Gemfile:
group :test do
# The basics
gem 'minitest'
gem 'minitest-rails'
# For better display
gem 'minitest-reporters'
# For managing testing data
gem 'factory_girl'
gem 'factory_girl_rails'
gem 'database_cleaner'
gem 'faker'
end
And run bundle
to install them all.
Regenerating Test Helper
I like to start everything from scratch, so I delete the /test
directory before I start.
rm -rf test
And then generate the test helper:
mkdir test
rails g minitest:install
Also, in config/application.rb
we want our application to know we're using Minitest, so this should be added in the Application
class:
config.generators do |g|
g.test_framework :minitest, spec: false, fixture: false
# add this if you're using FactoryGirl
g.fixture_replacement :factory_girl
end
Therefore, the generators will generate test files based on Minitest
instead of test/unit
. If you're using spec
framework just set it to true
, which I would not recommend because comparing with Minitest::Spec
, RSpec is definitely a better choice. Also, I prefer using factory to fixtures, so I set it to false as well.
Adding Rake Test
Add lib/tasks/test.rb
file and write:
task test: :environment do
Dir.glob('./test/**/test_*.rb').each { |file| require file }
end
Our First Test
The first test is fairly simple. Supposedly we have a Post
model and we want to test that it can be saved successfully. We create a /test/models/test_post.rb
file and write:
# require 'test_helper' does the same here
require 'minitest/autorun'
class TestPost < Minitest::Test
def test_create
@post = Post.create!(title: "Hello World")
assert_equal Post.last.title, @post.title
end
end
And run rake
command. We should see our test pass.
Conventions
Testing in Minitest basically follows several naming conventions.
File Structure
The /test
directory should look like an /app
directory, which means the same type of tests should be put together.
- test
- models
- controllers
- helpers
- integrations
- factories
- test_helper.rb
File Name
Files should start with test
, which means it should look like test_post.rb
.
Class & Method Name
Class name should be suffixed by Test
, and inherited from any Minitest class. Also, methods should always start with test
. For example:
# model
class TestPost < Minitest::Test
def test_create
# ...
end
end
# controller
class PostsControllerTest < ActionController::TestCase
def test_index
# ...
end
end
# helper
class PostsHelperTest < ActionView::TestCase
def test_my_helper
# ...
end
end
Note that from Rails 4.0 Minitest is set as default testing framework in Rails, so classes like ActionController::TestCase
actually uses Minitest as its base. In this case, methods like assert_equal
are accessible in these classes.
Assertions
When using assertions in tests, we should put the expected result in the front, and the actual returned value at the end. For example:
require 'minitest/autorun'
class FooHelperTest < ActionView::TestCase
def test_foo
assert_equal 'bar', foo
end
end
Method foo
is what we are testing, and we expect it to return a String 'bar'
. In this case, we should put 'bar'
first and then the method execution. This allows the reporter to output the correct format when something went wrong.
test_foo#FooHelperTest (1.34s)
Expected: "bar"
Actual: "foo"
test/helpers/test_foo_helper.rb:5:in `test_gig_help'
Adding Reporter
The default output for Minitest is horrible, so we're using minitest-reporters to make it look better. Adding this in the test_helper.rb
:
require 'minitest/reporters'
Minitest::Reporters.use!(
Minitest::Reporters::ProgressReporter.new,
ENV,
Minitest.backtrace_filter
)
And we'll see a better output and result.
Adding Setup & Teardown
In every test case, Minitest will call a setup
method to run any specified setup for the tests, and likewise, a teardown
method will be called after a test case. Every Class can have one setup
and teardown
method.
require 'minitest/autorun'
class TestPost < Minitest::Test
def setup
@post = Post.new(title: 'Hello World')
end
def test_save
@post.save!
assert_equal 'Hello World', @post.title
end
def teardown
Post.delete_all
end
end
Remember, all instance variables assigned in the setup
method are accessible in tests and teardown
.
Adding FactoryGirl & Database Cleaner
Some prefer fixtures in testing, but I prefer factory for its better management. To integrate factory in tests, we also need Database Cleaner for cleaning up data after each test. Here is what we need to add in test_helper.rb
require 'database_cleaner'
DatabaseCleaner.strategy = :transaction
module AroundEachTest
def before_setup
super
DatabaseCleaner.clean
DatabaseCleaner.start
end
end
class Minitest::Test
include FactoryGirl::Syntax::Methods
include AroundEachTest
end
It's a little tricky that we have to put the database cleaner in before_setup
method. In fact, this will allow all inherited setup
method in test classes to call this method, which ensures a clean state of database when each test case starts. For a better understanding of before_setup
you can checkout the Minitest documentation on this part.
For the usage of FactoryGirl please refer to its guide.
A Complete Example of test_helper.rb
ENV["RAILS_ENV"] = "test"
require File.expand_path("../../config/environment", __FILE__)
require "rails/test_help"
require "minitest/rails"
require 'minitest/reporters'
require 'database_cleaner'
Minitest::Reporters.use!(
Minitest::Reporters::ProgressReporter.new,
ENV,
Minitest.backtrace_filter
)
module AroundEachTest
def before_setup
super
DatabaseCleaner.clean
DatabaseCleaner.start
end
end
DatabaseCleaner.strategy = :transaction
class Minitest::Test
include FactoryGirl::Syntax::Methods
include AroundEachTest
end
class ActiveSupport::TestCase
ActiveRecord::Migration.check_pending!
end
I hope you have a better understanding on how to integrate Minitest into Rails :)