Switching from RSpec to Minitest

I like the “specification” style of testing – it makes more sense to me than the “assertion” style.

But I’ve recently switched from RSpec to Minitest (with Mocha).

Why?

RSpec is huge. It makes lovely tests that read like English, but it’s huge. Plus its recent switch from object.should == something to expect(object).to be == something negates some of the elegance of its syntax (although I understand why they did it).

Minitest is small. It’s bundled with Ruby (although I use the gem as it’s more up to date). And with Minitest::Spec, you can write in a manner very similar to RSpec.

But what about legacy code?

This can’t be fully automated, but the following commands ease the transition (on a Mac you need to install the rename command via Homebrew, however).

Firstly, rename your spec folder to test. Dump your spec helper if you have one and replace it with the equivalent test_helper.

Then, rename your specs to tests:

find . -name *_spec.rb -exec rename 's|_spec|_test|' {} \;

Next we need to do some global syntax replacement:

find . -name *_test.rb -exec sed -ie 's/context/describe/g' {} \;
find . -name *_test.rb -exec sed -ie 's/stub(/stubs(/g' {} \;
find . -name *_test.rb -exec sed -ie 's/double/stub/g' {} \;
find . -name *_test.rb -exec sed -ie 's/should_receive/expects/g' {} \;
find . -name *_test.rb -exec sed -ie 's/and_return/returns/g' {} \;
find . -name *_test.rb -exec sed -ie 's/expect(//g' {} \;
find . -name *_test.rb -exec sed -ie 's/).to be ==/.must_equal/g' {} \;

We’re swapping “context” for “describe”, “stub(some_object)” for “stubs(some_object)”, “double” for “stub”, “should_receive” for “expects”, “and_return” for “returns”, we’re dumping out the RSpec “expect” method (which has no equivalent in Minitest) and replacing “to be ==” tests with “.must_equal”.

Each test file then needs:

adding to the top (I rarely use a test_helper so I don’t accidentally load up stuff globally that’s only needed locally).

So after this, your tests won’t run. But the changes you need to make will be highlighted and you can go in by hand.

Some common changes:

expect(-> { subject.do_something }).to_raise SomeError
becomes

expect { | b | subject.find_field_definitions(&b) }.to yield_with_args(fields)

becomes

Mocha’s version of “as_null_object” is “stub_everything” – so

let(:client) { double('Client', name: 'Client').as_null_object }
becomes

let(:client) { stub_everything 'Client', name: 'Client' }

Most of the RSpec matchers have no equivalent:

expect(subject).to respond_to(:fields) becomes

subject.responds_to?(:fields).must_equal true

Not quite as elegant, but does the job. And did I mention it was faster?

UPDATED 2014-02-14 16:47

Mike Moore has pointed out that there are some nicer Minitest matchers I can use:

-> { subject.do_something }.must_raise SomeError

subject.must_respond_to :fields

So that’s what I get for not reading the docs properly. Thanks Mike.

Do you know what to do but not how it works?

Ever wanted to understand why Rails views work the way that they do? Why variables from your controllers are visible inside your views?

Sign up below to get a free 5 part email course on how Ruby on Rails view rendering works and gain a deep understanding of the Rails magic.

We will send you the course, plus the occasional update from this web-site. But no spam, we promise, and it's easy to unsubscribe