This post and the examples have been updated to the latest release of Jasmine, which is currently 3.5.
Jasmine is a simple, BDD-style JavaScript testing framework, but to benefit from the full power out of the framework, you need to know how to mock calls the Jasmine way.
Jasmine uses spies to mock asynchronous and synchronous function calls. As with most mocking frameworks, you can set the externally observed behavior of the code you are mocking.
Using Jasmine spies to mock code
Jasmine spies are easy to set up. You set the object and function you want to spy on, and that code won't be executed.
In the code below, we have a MyApp
module with a flag property and a setFlag()
function exposed. We also have an instance of that module called myApp
in the test. To spy on the myApp.setFlag()
function, we use:
spyOn(myApp, "setFlag");
It's a little strange that in Jasmine, you have to put the function you want to spy on in quotes, but that's the API.
When you spy on a function like this, the original code is not executed.
Returning values from Jasmine spies
Most of the time when setting up mocks, you want to set return values so you can test a specific code path. Again, this is easy to do with Jasmine.
Here, I show setting the return value of a function so we can test specific branches in the code and skip over the real getFlag()
function, which is hard-coded to return false.
The key piece is intercepting the getFlag()
function with the spy and setting the value the substituted function returns:
spyOn(myApp, "getFlag").and.returnValue(true);
Sometimes, setting a returnValue
isn't enough. If you need to replace the function you are mocking, you can use:
spyOn(myApp, "useFlagForSomething").and.callFake(function() {
return "I'm replacing the real function with this";
});
Using Jasmine spies to verify code was called
You can also call the original code with a spy. This may sound pointless (why set up a mock and then call the real code?) unless you think about interaction testing, where it's important to know that a function was or was not called.
The syntax to call the original code but still spy on the function is:
spyOn(myApp, "getFlag").and.callThrough();
The code below shows how you can verify a spied on function was called. We have a new function called reallyImportantProcess()
. We have to test to make sure it's being run under the right conditions, and we know it should run when getFlag()
returns false
, which is the default value. So we don't need to mock or change getFalse()
to take this code branch, but we do need to spyOn reallyImportantProcess()
to verify it gets called.
In this spy, we have lots of options. We could skip calling the real code, as we do below, or we could set up specific return values or substitute our own function for that function, or we could call the original code with callThrough()
. When you set up Jasmine spies, you can use any spy configuration and still see if it was called later with and toHaveBeenCalled()
.
You can also test that a spied on function was NOT called with:
expect(myApp.reallyImportantProcess).not.toHaveBeenCalled();
Or you can go further with your interaction testing to assert on the spied on function being called with specific arguments like:
expect(myApp.reallyImportantProcess).toHaveBeenCalledWith(123, "abc");
Testing async calls in Jasmine with jQuery's Deferred and helpers
Async calls are a big part of JavaScript. Most code dealing with async calls these day works through promises or callbacks.
Here, some test helper code can be used to mock promises being returned from an async call. I'm using jQuery's $.Deferred() object, but any promise framework should work the same.
You can mock an async success or failure, pass in anything you want the mocked async call to return, and test how your code handles it:
var JasmineHelpers = function () {
var deferredSuccess = function (args) {
var d = $.Deferred();
d.resolve(args);
return d.promise();
};
var deferredFailure = function (args) {
var d = $.Deferred();
d.reject(args);
return d.promise();
};
return {
deferredSuccess: deferredSuccess,
deferredFailure: deferredFailure
};
};
Here is some Jasmine spy code using these async helpers. Again, we use jQuery $.Deferred()
object to set up a function that calls out to a pretend async call named testAsync()
. It's invoked by callSomethingThatUsesAsync()
, which is the function we're testing. We want to mock the testAsync()
call (maybe it goes off to the database and takes a while), and we want to assert that when that testAsync()
call succeeds, callSomethingThatUsesAsync()
goes on to give us a text message saying it succeeded.
Since the function under test is using a promise, we need to wait until the promise has completed before we can start asserting, but we want to assert whether it fails or succeeds, so we'll put our assert code in the always()
function. The done()
, fail()
, and always()
functions are promise code - (actually, jQuery $.Deferred
code if you want to be technical) they are not specific to Jasmine.
Testing async calls in Jasmine with Jasmine's done() callback
Testing synchronous specs is easy, but asynchronous testing requires some additional work.
For example, the code below fails because Jasmine evaluates the expect()
piece before the testAsync()
function has finished its work. The setTimeout()
call forces a two second delay, but Jasmine has already moved on and failed the test before the setTimeout()
completes:
With Jasmine async testing, we have to call the async code in the beforeEach()
function that runs before each it()
function block within a describe()
function block.
We also have to let Jasmine know when the async function has completed by calling the special done()
callback function Jasmine provides. Here, we are passing this special done()
callback around so our code under test can invoke it.
This works, but changing the code under test to accept the done()
callback argument and invoke it may not be realistic. Why would you change your code under test just to make the testing framework happy?
Instead, you can use promises and call the special Jasmine done()
callback when your promise has resolved. Note that the Jasmine done()
callback is NOT the same as the promise's done()
callback. They just use the same function name.
Here, I'm using jQuery's $.Deferred() object for the promises, but this approach should work with any promises library.
You can even use the data returned from the promise in the test once it is resolved. Here we are passing the return value in the deferred.resolve()
call:
But of course, real-world applications can't get away with simply testing with setTimeout
and true
/false
flags, so here is a more real-world example.
It calls $.getJSON()
to go fetch some public JSON data in the beforeEach()
function, and then tests the returned JSON in the it()
block to make sure it isn't an empty object or undefined.
Testing async calls in Jasmine with async/await
With version 2.8 and later of Jasmine and your compiler that supports async/await (e.g., Babel, TypeScript), you can change this to be more readable:
Much cleaner!