Friday, August 1, 2014

To Kill A Mocking Stub

This week is clearly dedicated to RSpec and testing, as I've been working on Connect Four game and testing it most of the week. One of the things I've long wanted to clarify for myself is how to create fake objects and methods while testing, and what the difference is in how these fakes are created.

Specifically, what is the difference between stubs and mocks? What does it mean if we are stubbing a method vs. mocking an object? Both are related to creating some fake data and expectations regarding how our code show behave. There is also some confusion regarding the terminology.

According to RSpec docs, a method stub is "an instruction to an object (real or test double) to return a known value in response to a message". A message expectation is "an expectation that an object should receive a specific message during the course of a code example".

The main difference lies in the message expectation part. Both stubs and mocks operate with test doubles, which are just like stunt doubles standing in for some celebrity. However, when a stub creates a test double for a method, it does not create an expectation that the method will be called. In other words, it just returns a specified result for the method without calling an actual method. On the opposite, a mock does create an expectation that the method will be called. As a result, stubs do not fail a test (because they do not trigger an actual method), but mocks can fail (if a triggered method does not return expected results or if a method is not called).

Here is a simplified and a bit artificial example from Connect Four game. Let's say we want to check whether a given color was placed in a given location at a board. Note that the set_color_to function is called twice on purpose:

def set_color_to(color)
    @color = color
end

def set_colors_at(x,y)
    @matrix[x][y] = set_color_to("blue") unless !@matrix[x][y].nil?
end

def iam_a_weird_function
    set_color_to("red")
    x,y = 1,2
    set_colors_at(x,y)
end

def get_color_at(x,y)
    @matrix[x][y]
end

And the tests look like so:

let(:board) { Board.new }

describe "stubs vs mocks" do

    it "uses a stub" do
        allow(board).to receive(:set_color_to).and_return("green")
        board.iam_a_weird_function
        expect(board.get_color_at(1,2)).to eql "green"
    end

    it "uses a mock" do
        # expect(board).to receive(:set_color_to).and_return("green")
        expect(board).to receive(:set_color_to).and_return("green").exactly(2).times
        board.iam_a_weird_function
        expect(board.get_color_at(1,2)).to eql "green"
    end

end

The first test passes because we stub the set_color_to function, which returns a predetermined value of green. Even though the set_color_to function is called twice in the actual code, it doesn't matter in the test because a stub doesn't require a method to be actually called.

The second test fails at line 12 and complains that it expected to receive a message once with any arguments; instead, a message was received twice. This is happening because a mock has an expectation that the set_color_to function will be called. Line 13 fixes this by adding exactly(2).times option, which specifies how many times we expect to receive a message.   

This example shows functional differences between stubs and mocks; what is the conceptual difference? Testing is based on the idea that each test should test a single behavior; using stubs and mocks helps to achieve this. Thus, a stub could be used to check if a correct view is being rendered in an application, but it wouldn't check if an appropriate method gets called. The latter should be tested in a separate function.

No comments :

Post a Comment