home articles hire me

What is Minitest::Spec?

In my previous post, I talked a lot about how Minitest comes in various syntax flavors. One flavor I did not cover much is Minitest’s spec extension.

Before I dive in with a dedicated post about assertions, I want to cover this RSpec-style way of writing tests.

Minitest syntax flavors: a recap

As I wrote in my previous post, Minitest comes in multiple flavors:

Each flavor changes the syntax we use to define our test files and our test examples. The changes in the DSL are minimal, in the sense that they all look familiar to Ruby developers.

Assertions are another matter.

While Rails adds a handful of assertions (mostly around database changes), both Minitest and Rails rely on the following paradigm:

Check that the expected assertion holds true against the actual result.

On the contrary, Minitest::Spec relies on the (RSpec-like) opposite paradigm:

Check that the actual behavior matches the expected result.

I know this sounds “po-tay-to, po-tah-to”, but bear with me for one sec.

Plain Minitest flavor:

  class UserTest < Minitest::Test
    def setup
      @user = User.new(first_name: "buffy", last_name: "summers")
    end

    def test_returns_the_full_name
      assert_equal "buffy summers", @user.full_name
    end
  end

Minitest::Spec flavor:

  class UserTest < Minitest::Spec
    before do
      @user = User.new(first_name: "buffy", last_name: "summers")
    end

    it "returns the capitalized full name" do
      expect(@user.full_name).must_equal "Buffy Summers"
    end
  end

For someone who comes from RSpec, this looks very familiar:

Using Minitest::Spec also allows me to organize my tests into describe blocks for different contexts.

  class UserTest < Minitest::Spec
    before do
      @user = User.new(first_name: "buffy", last_name: "summers")
    end

    it "returns the capitalized full name" do
      expect(@user.full_name).must_equal "Buffy Summers"
    end

    describe "when the user has a two-word last name" do
      before do
        @user = User.new(first_name: "buffy", last_name: "jane summers")
      end

      it "does not return the capitalized full name" do
        expect(@user.full_name).wont_equal "Buffy Jane Summers"
      end
    end
  end

Expectations can also be slightly abstracted away with the following syntax:

  it "returns the capitalized full name" do
    _(@user.full_name).must_equal "Buffy Summers"
  end

How to enable Minitest::Spec?

In Ruby applications

In plain Ruby files, there’s no need to configure anything at the application level. Minitest comes with both flavors, plain and spec, by default.

The only gotcha is that test classes need to inherit from Minitest::Spec instead of Minitest::Test. If you didn’t find this documented in the official repository, you’re not alone. It’s not documented at all. I found it in the source code while investigating why my spec-style tests weren’t working.

  class UserTest < Minitest::Spec
    ...
  end

In Rails applications

In Rails, I’ve found that you need to require "minitest/spec" and extend Minitest::Spec::DSL to the ActiveSupport::TestCase class.

Here’s an extract from my test_helper.rb file:

  # frozen_string_literal: true

  require "rails/test_help"
  require "minitest/spec"

  module ActiveSupport
    class TestCase
      extend Minitest::Spec::DSL
    end
  end

On the other hand, there’s no need to change the inheritance of your test classes to Minitest::Spec. The inheritance in Rails is already quite complicated on its own, though:

Sharp knives and gotchas

Ruby and Rails allow you to do some weird things at your own risk. Mixing setup do and before do in the same file is one of them.

Don’t do this. This is for educational purpose only.

In plain Ruby, setup and before will work alongside one another without a hurdle.

In Rails, both will work if used exclusively in one file. If you mix them in one file, you have two possible outcomes:

  require "test_helper"

  class UserTest < ActiveSupport::TestCase
    before do
      # will get executed
    end

    test "some method" do
      # will pass
    end

    describe "a nested context" do
      setup do
        # will not be executed
      end

      it "returns some result" do
        # will potentially fail
      end
    end
  end

Now that we’re more familiar with Minitest::Spec, we have access to a new way of writing assertions, such as must_equal, must_match, and others. I’ll cover those in a future post.

Wrapping up

I hope this post helped clarify the basics of the Minitest::Spec syntax.

The TL;DR is: