Use Minitest for Your Next Rails Project

Minitest is a fast, easy to read alternative to RSpec for writing Rails tests, but it can be confusing at first. Here’s how I set up Minitest with Rails, and the gotchas I encountered along the way.

Minitest seems to be enjoying a spike in popularity; my interest was piqued by Brandon Hilkert’s article, 7 Reasons I’m Sticking with Minitest and Fixtures in Rails. But when it came time to actually setting up my Rails project, I found Minitest+Rails documentation to be outdated or lacking.

tl;dr – Jump straight to the code recommendations.

Minitest output
Actual Minitest output from one of my recent Rails projects.

Minitest is confusing!

If you decide to jump on the Minitest bandwagon, you’ll probably run into these gotchas too. Here is what I discovered.

TestUnit and Minitest are the same thing… except not. Test::Unit was the name of the default Rails testing framework up through Rails 3. Starting with Rails 4, the core functionality and assertions were replaced by Minitest, but confusingly, in some places the name “test unit” remains.1 Just be assured that if you are using Rails 4 or higher, then “test unit” simply means Minitest, with some Rails-specific syntactic sugar sprinkled on top.

If you search the web and come across blog posts, Stack Overflow answers, or gems that refer to Test::Unit, they are probably talking about Rails 3 and below. Pay close attention to the publication date and the Rails version referenced to make sure those gems and recommendations are still applicable.

Minitest has two different syntaxes. Minitest is a single testing framework, but it ships with two completely different DSLs: the “assert” (or “xUnit”) style DSL and the BDD (behavior-driven development) style. I am only concerned with the assertion style, since that is what is promoted by Rails itself, and is what differentiates Minitest from RSpec. My reasoning: if I wanted to write BDD style, I’d just stick with RSpec!

Minitest’s default output is a step backwards. Minitest makes a bad first impression, because its default output on the console is pretty bland: no color! This is something I took for granted as an RSpec user. Surprisingly, the tutorials I found for Minitest did not address this.2 Luckily I discovered the minitest-reporters gem, which does everything I need (including a nifty progress bar).

The directory structure is not immediately obvious. Minitest itself does not care how you organize or name your tests, but the Rails ecosystem uses a certain set of conventions. Unfortunately these conventions have changed recently, so I found myself getting conflicting suggestions from various places online. Rails previously structured its tests based on the type of test (unit vs functional), but now the structure is basically a mirror of the app/ directory (controllers, models, helpers). I’ve documented the correct project layout below.

The “Rails way” to write tests differs from Minitest tutorials. Rails documentation and tutorials declare tests as:

test "my feature" do

Whereas the Minitest way is:

def test_my_feature

Likewise, the Rails convention is to name test classes as e.g. UserTest, versus Minitest’s TestUser. Both styles will work fine, but for consistency with the Rails generators, I find it’s best to stick with the Rails conventions. Just be aware that the test "my feature" do ... end style is Rails syntactic sugar and is not available if you are using Minitest outside of Rails.3

Minitest’s mocking library is not great. Minitest comes with its own Minitest::Mock library, but it is very basic. If you are going to do any serious mocking or stubbing in your tests, you will probably need to go elsewhere. My recommendation is the mocha gem, which works great with Rails and Minitest.

Minitest or MiniTest? If all that wasn’t confusing enough, there is also the spelling issue: Minitest was also spelled as MiniTest earlier in its lifetime. The correct capitalization in the latest version is Minitest (lowercase “t”).

Set up your project

Still interested in using Minitest? Here’s a quick rundown on how to set up your next Rails project.

Gemfile. No additions to the Gemfile are strictly required, since Rails ships with the bare minimum you need to write tests using Minitest. However I find guard invaluable for my TDD workflow, and capybara, poltergeist, mocha and shoulda-matchers come in handy for most Rails projects.

group :development do
  gem "guard", ">= 2.2.2", :require => false
  gem "guard-minitest", :require => false
  gem "rb-fsevent", :require => false
  gem "terminal-notifier-guard", :require => false

group :test do
  gem "capybara"
  gem "connection_pool"
  gem "launchy"
  gem "minitest-reporters"
  gem "mocha"
  gem "poltergeist"
  gem "shoulda-context"
  gem "shoulda-matchers", ">= 3.0.1"
  gem "test_after_commit"

test/test_helper.rb. The test_helper.rb file is responsible for loading Rails and configuring the test framework. In my projects I’ve added best practices for using Minitest with Capybara and Poltergeist.

ENV["RAILS_ENV"] ||= "test"
require File.expand_path("../../config/environment", __FILE__)
require "rails/test_help"
require "mocha/mini_test"

# Improved Minitest output (color and progress bar)
require "minitest/reporters"

# Capybara and poltergeist integration
require "capybara/rails"
require "capybara/poltergeist"
Capybara.javascript_driver = :poltergeist

class ActiveSupport::TestCase
  fixtures :all

class ActionDispatch::IntegrationTest
  include Capybara::DSL

# See:
class ActiveRecord::Base
  mattr_accessor :shared_connection
  @@shared_connection = nil

  def self.connection
    @@shared_connection || => 1) { retrieve_connection }
ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection

Project layout. Use this folder structure for organizing your tests. The controllers, helpers, mailers, and models folders are used for testing their /app counterparts. The odd one out is lib, which goes in test/unit/lib.

  integration/   <- Capybara tests go here
  unit/lib/      <- Tests for your /lib code

Guardfile. Run guard and it will watch your filesystem and automatically run the corresponding tests whenever you save a file. This is super handy for a quick TDD workflow. Here’s the Guardfile configuration that makes this work:

guard :minitest, :spring => true do
  watch(%r{^app/(.+)\.rb$})                               { |m| "test/#{m[1]}_test.rb" }
  watch(%r{^app/controllers/application_controller\.rb$}) { "test/controllers" }
  watch(%r{^app/controllers/(.+)_controller\.rb$})        { |m| "test/integration/#{m[1]}_test.rb" }
  watch(%r{^app/views/(.+)_mailer/.+})                    { |m| "test/mailers/#{m[1]}_mailer_test.rb" }
  watch(%r{^app/workers/(.+)\.rb$})                       { |m| "test/unit/workers/#{m[1]}_test.rb" }
  watch(%r{^lib/(.+)\.rb$})                               { |m| "test/unit/lib/#{m[1]}_test.rb" }
  watch(%r{^lib/tasks/(.+)\.rake$})                       { |m| "test/unit/lib/tasks/#{m[1]}_test.rb" }
  watch(%r{^test/test_helper\.rb$}) { "test" }


Making the switch from RSpec to Minitest was a bit harder than I expected, but that was mostly due to outdated or scattered documentation. The framework itself is simple and a breeze to use.

For the most part, testing tools that I enjoyed from the RSpec ecosystem, like guard, capybara, simplecov, and vcr also work great with Minitest, so I don’t feel like I’m missing anything. I’m also finding that I prefer fixtures over factory_girl, although that’s perhaps the subject of another article.

By the way, if you’d like to use the project setup I’ve outlined in this article, check out my rails-template project. It makes setting up a new Rails app with all these Minitest goodies as simple as:

rails new blog \
  -d postgresql \

Happy testing!

  1. The railtie for loading the Minitest-based testing framework is still named rails/test_unit/railtie. Likewise, to disable Minitest in Rails, you pass the --skip-test-unit flag when running rails new

  2. Some articles mention minitest/pride, which makes the output rainbow-colored. Entertaining, but not very practical. 

  3. More specifically, these bits of extra syntax are provided by ActiveSupport. For example, the test DSL is defined in ActiveSupport::Testing::Declarative