As long as the code we're testing remains relatively simple, Mocha and Expect.js are all that we need to test it. However, the code rarely remains simple, and there are two complications in particular that can make us need another tool, known as a mocking
library, to let us create fake versions or mocks of our objects.
First, let's imagine we want to test the following method of an imaginary ExampleModel
class:
foo: function() { this.bar += 1; this.baz(); }
Now, we'll want to test whether our foo
method calls the baz
method. However, at the same time, we will (presumably) already have a separate test of the baz
method itself. This leaves us with a dilemma: how can we test that foo
calls baz
without repeating the test code for baz
inside the test code for foo
?
Alternatively, let's consider another imaginary method that we might want to test:
fetchThenDoFoo: function() { this.fetch().done(this.foo); }
In this case, we want to test whether foo
is called after the fetch
operation completes, but to truly test this, we'd need to actually fetch a Model
class from the server by using AJAX. This, in turn, would make our tests require an active server, making them significantly slower and opening up the possibility of the tests causing side effects on our server.
The solution to both of these problems is to use a mocking library such as Sinon.js
. To do this, simply download sinon.js from http://sinonjs.org/ and then include this file (via a script
tag) on your test running the HTML page.
Once Sinon is available, we can use it to solve our testing problems. First, let's use it to create a special kind of mock called stub
for a test of our foo
method as follows:
describe('foo', function() { var bazStub, example; beforeEach(function() { example = new ExampleModel(); // Replace the real "baz" with a fake one that does nothing bazStub = sinon.stub(example, 'baz'), }); it('calls baz', function() { example.foo(); expect(bazStub.calledOnce).to.be(true); // did foo call baz? }); afterEach(function() { // Restore the original baz (in case another test uses it) baz.restore(); }); });
As you can see in the preceding code, we were able to use Sinon to create stub
that replaced the normal baz
method. While this stub
didn't actually do anything, it did keep track of how many times it was called (as well as which arguments were used, although we didn't test this), which let us write a test that ensured that foo
would call baz
without having to repeat any of the test code for baz
.
For our second problem, that of testing an AJAX method, we could use an AJAX-specific mock tool such as MockJax. However, Sinon is so powerful that we really don't need to use anything else; consider the following test:
describe('fetchThenDoFoo', function() { var fetchStub, fooStub, example; beforeEach(function() { example = new ExampleModel(); // Replace the real "fetch" with a fake one that returns an // already-resolved $.Deferred var deferred = new $.Deferred().resolve(); fetchStub = sinon.stub(example, 'fetch').returns(deferred); // Since we only want to test whether or not foo was called, // we can also use stub for it fooStub = sinon.stub(example, 'foo'), }); it('calls foo after fetch completes', function() { example.fetchThenDoFoo(); expect(fooStub.calledOnce).to.be(true); }); afterEach(function() { // Restore the original versions of our stub functions fetchStub.restore(); fooStub.restore(); }); });
In this example, we used two stub
functions. We used the fooStub
function in a manner similar to how we used the bazstub
function in the previous example, to check whether or not foo
got called, but our fetchStub
served a different purpose. By chaining a returns
method call off of our stub's creation, we created a stub
function that (unlike the previous stub
functions) actually did something: it returned our resolved deferred function. Since jQuery treats a resolved deferred function the same way as it treats a completed AJAX call (by invoking any done code for the call), we simulated the return of an AJAX call without involving an actual server.
Sinon has many other useful methods related to stub
, as well as other types of mocking functions such as spy
and mock
, all of which are very well documented on their website. Sinon also has a feature known as sandbox
, which can allow you to eliminate all of the baz.restore()
code by automatically activating it after every test run (when sandbox
is cleaned up). Again, you can find all the details of this feature on Sinon's website.
3.147.53.119