Using sinon mocks

In the previous posts we had a look at sinon spies and stubs. There is one more technique we can use in order to orchestrate our test dependencies: mocks.

Let’s start by breaking the Calculator’s isReady method. That method was supposed to return true if the battery is charged, false otherwise:

Calculator.prototype.isReady = function() {
    return false;
};

We just broke it, so it always returns false without even checking the battery status. If we run the tests, we’ll see that out of the two relevant tests, one is passing and one is failing:

      x should be ready when the battery is charged

      ✓ should not be ready when the battery is empty

The test that succeeds is the one that proves that the calculator does not work when the battery is empty. The thing is, our calculator never works with this implementation. Luckily one test is breaking, so we are safe. But why did the other test pass?

We are using a stub in that test. Stubs are pre-programmed functions. If you call them, they will do as they have been programmed. But if you don’t call them in the programmed way, or not at all, then nothing happens. This is actually a good thing, because the test shouldn’t enforce any implementation details. We should test behavior and not implementation and that’s what the stubs provide.

The mocks work in a different way. Let’s see how that looks like:

describe('isReady', function() {
    var mockBattery;
    var calculator;

    beforeEach(function() {
        // arrange
        var bell = sinon.spy();
        var battery = {
            getLevel: function() {}
        };

        mockBattery = sinon.mock(battery);
        calculator = new Calculator(bell, battery);
    });

    it('should be ready when the battery is charged', function() {
        // arrange
        mockBattery.expects('getLevel').returns(100);

        // act
        var isReady = calculator.isReady();

        // assert
        expect(isReady).to.be.true;
        mockBattery.verify();
    });

    it('should not be ready when the battery is empty', function() {
        // arrange
        mockBattery.expects('getLevel').returns(0);

        // act
        var isReady = calculator.isReady();

        // assert
        expect(isReady).to.be.false;
        mockBattery.verify();
    });
});

Let’s see the differences with the stubs:

  • The mock wraps the entire battery object. You need to have at least the definition, if not the actual implementation, of the Battery object in order to mock it. For the stub, that was not necessary, although it was nice to have there as well.
  • At the end of the test, the mock is verified. This arguably violates the “one assertion per unit test” rule but it is necessary.
  • Last and most important: the mock expectations that are defined upfront are not optional; every single expectation of the mock has to occur, otherwise the mock verification will fail the test.

If the stubs represent pre-programmed behaviors, the mocks represent pre-programmed behaviors but also expectations.

Running the tests like this we’ll have two failures, because nobody is calling the getLevel method of the battery object:

ExpectationError: Expected getLevel([...]) once (never called)

You can read in this article by Martin Fowler more about mocks vs stubs. Using mocks make the tests stricter, but that’s because they enforce implementation details. The general rule is that tests should focus on behavior, not implementation. This means that you shouldn’t use a mock, unless for some reason you can’t avoid it. Stubs should be enough.

This post concludes this series of articles on unit tests. Coming up, we’ll have a look at browser testing with webdriverIO.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s