Faking and verifying behavior with Sinon.JS mocks

The final test double abstraction that we will cover in this book is the test mock. Mocks replace function behaviors like stubs, observe method calls like spies and stubs, and additionally verify function behaviors. Essentially, mocks are a "one-stop shop" for faking and testing methods.

Deciding when to mock

So, when should we use mocks? The Sinon.JS mock API documentation (http://sinonjs.org/docs/#mocks) starts with the appropriate use cases for mocks:

"Mocks should only be used for the method under test. In every unit test, there should be one unit under test. If you want to control how your unit is being used and like stating expectations upfront (as opposed to asserting after the fact), use a mock."

The documentation cautions that mocks should be avoided in many situations:

"Mocks come with built-in expectations that may fail your test. Thus, they enforce implementation details. The rule of thumb is: if you wouldn't add an assertion for some call specific, don't mock it. Use a stub instead. In general you should never have more than one mock (possibly with several expectations) in a single test."

We have a bias in this book towards Sinon.JS stubs for the reasons just discussed and because of the following:

  • The Chai and Sinon-Chai adapter libraries allow us to write test assertions on stubs that are concise, expressive, and readable
  • The Sinon.JS mock expectation API is less flexible than using Chai assertions against the stub API

At the same time, mocks are stubs. So, tests can mix and match preprogrammed Sinon.JS mock expectations with subsequent Chai stub assertions. Ultimately, after we have finished reviewing the details of stubs and mocks in this chapter, we leave the choice of abstraction up to the developer and the specific testing scenario at hand.

The mock API

Sinon.JS mocks (http://sinonjs.org/docs/#mocks-api) implement the spy and stub APIs and additionally provide expectations that verify application behavior. We'll begin with a brief discussion of the core mock methods:

  • sinon.mock(obj): This method mocks all the methods of obj and returns a mock object
  • mock.expects(methodName): This method creates an expectation for the specified method of the mocked object
  • mock.verify(): This method examines and verifies that all expectations were met and throws exceptions on assertion failures
  • mock.restore(): This method unwinds and removes all mocked modifications of the underlying object that is being tested

After we have mocked an object, the usual workflow is to call mock.expects() on one or more methods and configure expectations for a mock.verify() call later. For the complete expectations list, see http://sinonjs.org/docs/#expectations. A sampling of some useful expectation methods includes the following:

  • expectation.atLeast(num), expectation.atMost(num), and expectation.exactly(num): These mocked methods should be called at least/at most/exactly num times respectively
  • expectation.never(), expectation.once(), expectation.twice(), and expectation.thrice(): These are the helpers specifying common assertions for the number of times the mocked method was called
  • expectation.withArgs(arg1, arg2, ...) and expectation.withExactArgs(arg1, arg2, ...): Every call to the mocked method has at least/exactly the parameters specified in the expectation respectively
  • expectation.on(obj): This mocked method should be called with obj as the context (this) variable
  • expectation.verify(): This method runs assertions on a specific expectation (as opposed to mock.verify() that confirms all expectations)

In the following code snippet, we create our mock object around obj and declare the expectation that multiply will be called two to four times and that the first argument to every call will be 2. We then call multiply three times with the appropriate parameters. Finally, a single mock.verify() call checks if all of the mock expectations were met:

// Our (now very familiar) object under test.
var obj = {
  multiply: function (a, b) { return a * b; },
  error: function (msg) { throw new Error(msg); }
};

it("mocks multiply", function () {
  // Create the mock.
  var mock = sinon.mock(obj);

  // The multiply method is expected to be called:
  mock.expects("multiply")
    .atLeast(2)    // 2+ times,
    .atMost(4)     // no more than 4 times, and
    .withArgs(2);  // 2 was first arg on *all* calls.

  // Make 3 calls to `multiply()`.
  obj.multiply(2, 1);
  obj.multiply(2, 2);
  obj.multiply(2, 3);

  // Verify **all** of the previous expectations.
  mock.verify();

  // Restore the object.
  mock.restore();
});
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.90.185