Handy gems for minitest-rails part2

Mocks and stubs are not new concepts that have been introduced in minitest. If you want to get detailed understanding about what are mocks and stubs you should read mocks aren’t stubs written by Martin Fowler. To achieve mocking and stubbing in minitest-rails you don’t need to include any separate gem like rspec-mocks in rspec. Let see how to do work with mocks and stubs in minitest then check what are gems available to add extra functionality.

Method stubbing
Stubbing a method means, set a pre-defined return value of a method in test-case. Let’s check how to do method stubbing in minitest:

  # app/models/user.rb
  class User < ActiveRecord::Base
    def foo
      "Executes foo"
    end

    def bar
      "Executes bar"
    end

    def new_method
      foo
      bar
    end
  end
  it "stubbing known method" do
    user = User.new
    user.stub(:foo, "Return from test foo") do
      user.new_method.must_equal "Executes bar"
    end
  end

In this example, I stub foo and check what would be the output of new_method. As matter of fact it won’t call actual foo method instead it calls stub foo method. Now that we have  seen how to stub and instance method, let check how to stub on any instance.

minitest stub any instance:

Using this gem you can stub a method on any instance of a class. The below example demonstrates how to do that:

  # app/models/user.rb
  class User < ActiveRecord::Base
    def foo
      "Executes foo"
    end

    def bar
      "Executes bar"
    end

    def new_method
      foo
      bar
    end
  end
  it "stubbing on any instance" do
    User.stub_any_instance(:foo, "Return from test foo") do
      p User.new.foo
      User.new.foo.must_equal "Return from test foo"
    end
    p User.new.foo
  end

Output:
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
Run options: –seed 36985

# Running tests:

“Return from test foo”
“Executes foo”

Notice that the stub method on any instance of User is valid only in the stub_any_instance block.

minitest-stub-const:

As name suggests you can stub constants of a class. Here is the example:

  it "stubbing a constant" do
    User.stub_const(:CONSTANT, "Test constant") do
      User::CONSTANT.must_equal "Test constant"
    end
  end

Here CONSTANT is a constant defined on User class. Using minitest-stub-const we can not only stub constants but also class methods. Here is the example:

  # app/models/user.rb
  def self.add
    "User created on development"
  end

  def add_user
    User.add
  end
  it "stub a class method" do
    m = MiniTest::Mock.new
    m.expect(:add, "Created user from testing")

    User.stub_const(:User, m) do
      User.new.add_user.must_equal "Created user from testing"
    end

    m.verify
  end

In above example, I stub User with mock object m. Now whenever User is referred, it is replaced with the mock object m. Now, we call the method add on the Mock object but this method is already mocked using m.expect(:add, "Created user from testing").

Mocking:

Mocking an object is one of the ways to do unit testing. Mock objects are the objects that we use in a test environment instead of real objects. We can create mocks using double in rspec to create dummy objects, where as in minitest you can do as Minitest::Mock.new which creates mock object. On mock object we can set expected messages. Here is an example:

  it "mocking a method" do
    user = Minitest::Mock.new
    user.expect(:another_method, "from test bar")

    user.another_method.must_equal "from test bar"

    user.verify
  end

You can also mock objects using minitest firemock as follows:

  it "mocking a method" do
    user = Minitest::FireMock.new("User")
    user.expect(:another_method, "from test bar")

    user.another_method.must_equal "from test bar"

    user.verify
  end

The only difference between Minitest::Mock and Minitest::FireMock is, when you try to mock undefined method then Minitest::FireMock raises an exception whereas Minitest::Mock won’t.

Hope this helps while you writing mocks and stubs in minitest. Any suggestions, queries would be welcome.