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 :)

Reference