Unit Testing Async Calls and Promises with Jasmine

Anne Zhou
DailyJS
Published in
3 min readOct 25, 2017

Promises can often be puzzling to test due to their asynchronous nature. After looking at Jasmine documentation, you may be thinking there’s got to be a more simple way of testing promises than using setTimeout. This post will show you a simple approach to test a JavaScript service with an exported function that returns a promise.

Unit testing is all about isolating the method that you want to test and seeing how it behaves when it takes some parameters or makes other function calls. Methods usually have dependencies on other methods, and you might get into a situation where you test different function calls within that one method. Doing so breaks encapsulation and should be avoided when possible. For any one function, all you want to determine is whether or not a function returns the expected output given a set of inputs and whether it handles errors if invalid input is provided.

With this example, we want to test the exposed fetchPlaylistsData function in playlistsService.js.

As you can see, the fetchPlaylistsData function makes a function call from another service. This is where a mock comes in handy. A mock is basically a fake object or test data that takes the place of the real object in order to run examples against the spec. In Jasmine, mocks are referred as spies that allow you to retrieve certain information on the spied function such as:

  1. The arguments passed to the function
  2. What value the function returns
  3. How many times the spied function was called

For our unit test, we want to test if the fetchPlaylistsData function calls fetchData from apiService. Before we begin writing the spec, we create a mock object that represents the data structure to be returned from the promise.

We require this at the top of our spec file:

const promisedData = require('./promisedData.json');

We’re going to use the promisedData object in conjunction with spyOn. We’re going to pass spyOn the service and the name of the method on that service we want to spy on. Since we are performing an async operation, we should be returning a promise from this function. By chaining the spy with and.returnValue, all calls to the function will return a given specific value. We are supplying it with a fake response to complete the function call on its own. Because original function returns a promise the fake return is also a promise: Promise.resolve(promisedData).

spyOn(apiService, 'fetchData').and.returnValue(Promise.resolve(promisedData));

Once you have the spy in place, you can test the full flow of how the fetchPlaylistsData function, that depends on apiService.fetchData, runs without relying on actual API responses. It’s important to note that we want to test playlistsService.fetchPlaylistsData and not apiService.fetchData.

apiService.fetchData is essentially a hidden input to playlistsService.fetchPlaylistsData which is why we fake it just like other inputs for playlistsService.fetchPlaylistsData function call. We do not want to test API responses because they are external to our app.

You can check on the spied on function in .then of the async call. This is where you can use toHaveBeenCalled or toHaveBeenCalledWith to see if it was called. You should also check if the result of the promise is the expected output you want to see via the toEqual matcher.

expect(apiService.fetchData).toHaveBeenCalledWith(video);
expect(result).toEqual(promisedData);

Because we’re testing an async call, in your beforeEach or it block, don’t forget to call done. The test runner will wait until the done() function is called before moving to the next test.

There’s more you can do with spies like chaining it with and.callThrough and and.callFake when testing promises, but for the most part, that’s it!

You have a working, tested JS service! 🎉

Read more:

--

--