AWS Developer Tools Blog

From RSpec to Minitest

One of my favorite aspects of working with Ruby is how natural it is to write tests for. The Ruby community does an excellent job of encouraging authors to produce well tested code. There is a plethora of well supported tools to choose from. I like to joke that new Ruby developers write Micro test frameworks instead of “Hello World!”.

Much of the Ruby code I have maintained uses RSpec. Lately I have been spending some time with Minitest. You may have worked with Minitest — it ships as part of the Ruby standard library.

Why bother learning another testing framework when your current tool suits your needs? My answer is why not? It’s always good to expand your horizons. I have found learning a new testing tool expands my ability to write good tests. I pick up new patterns and the context switch forces to me to question my standard testing approaches. As a result, I tend to write better tests.

Minitest::Spec

Minitest::Spec does a great job of bridging the gap between RSpec-style specifications and Minitest-style unit tests.

Here is an example RSpec test file:

require 'spec_helper'

describe MyClass do
  describe '#some_method' do
    it 'returns a a string' do
      MyClass.new.should be_a(String)
    end
  end
end

And the same thing using Minitest::Spec:

require 'test_helper'

describe MyClass do
  describe '#some_method' do
    it 'returns a string' do
      MyClass.new.some_method.must_be_kind_of(String)
    end
  end
end

Matchers

The primary difference above is how you make assertions. RSpec-style should matchers can be converted to Minitest expectations with ease. The table below gives a few examples.

RSpec Matcher Minitest Matcher
obj.should be(value) obj.must_be(value)
obj.should be_empty obj.must_be_empty
obj.should be(nil) obj.must_be_nil
obj.should equal(value) obj.must_equal(value)
lambda { … }.should raise_error(ErrorClass) lambda { … }.must_raise(ErrorClass)

See the Minitest api documentation for more expectations.

Mocks

Mocks (and stubs) are where the two testing libraries differ the most. RSpec provides doubles; Minitest provides a Mock class.

@mock = Minitest::Mock.new

You can set expectations about what messages the mock should receive. You name the method to expect, what to return from the mock and then optionally the arguments the method should receive. Given I need a mock for a user, and I expect the user’s delete method will be called, I could do the following:

user = Minitest::Mock.new
user.expect(:delete, true) # returns true, expects no args

UserDestoyer.new.delete_user(user)

assert user.verify

Calling #verify is necessary for the mock to enforce the expectations. RSpec makes this a little easier, but its not a huge adjustment.

Stubs

Stubs are pretty straight forward. Unlike RSpec, the stub lasts only till the end of the block. You also cannot stub methods that don’t exist yet.

Time.stub :now, Time.at(0) do
  assert obj_under_test.stale?
end

Summary

Minitest works well and I’m impressed by how fast it run tests. Coming from RSpec you may find yourself missing features. Instead of trying to find exact replacements, consider using plain old Ruby solutions instead. You may find you write better tests. I still enjoy working with RSpec and appreciate it for its strengths, but you should also consider giving Minitest a spin.