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.
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:
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.
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 objectmock.expects(methodName)
: This method creates an expectation for the specified method of the mocked objectmock.verify()
: This method examines and verifies that all expectations were met and throws exceptions on assertion failuresmock.restore()
: This method unwinds and removes all mocked modifications of the underlying object that is being testedAfter 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 respectivelyexpectation.never()
, expectation.once()
, expectation.twice()
, and expectation.thrice()
: These are the helpers specifying common assertions for the number of times the mocked method was calledexpectation.withArgs(arg1, arg2, ...)
and expectation.withExactArgs(arg1, arg2, ...)
: Every call to the mocked method has at least/exactly the parameters specified in the expectation respectivelyexpectation.on(obj)
: This mocked method should be called with obj
as the context (this
) variableexpectation.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(); });
52.14.82.217