One of the nice features about Angular is the testability. The dependency injection makes it easy to test components in isolation and stub out dependencies where necessary. However, it is not immediately obvious how to test asynchronous code. To illustrate how this works I am going to create an example (with asynchronous code) and test this using Jasmine.
I am going to create a list of users and allow the user to remove these. The html code might look something like this:
The controller in this case could delegate the actual http call to a service.
(Notice that this code is problematic - no error handler is attached to the promise.)
So how do we go about testing this? There are actually 2 steps that we’re trying to test here. First, when we call this function we can test that it calls
userService.delete with the given user. Second, when this promise is resolved we want to test that the user is removed from the original list of users.
Setup the test
If you have never tested an angular controller with Jasmine it might seem like you need to do a large amount of setup, but it’s actually pretty easy and after a while it becomes second nature.
In order to create the controller in our test we need to specify a scope for the controller to use. In the regular application flow angular will supply the (parent) scope, but in the test we always need to specify the scope. We do this by creating a new scope with
This is all in order to create the controller. The other dependency that we want to stub out is the
userService. I am therefore going to inject this service and store a reference to it - I will stub out the
delete function later on.
Notice that I am injecting the
_userService_ - this is a convenience trick that we can use in order to assign the injected class to a variable of the same name.
Write the actual test
Let’s tackle the first step - checking that the
userService is actually called with the specified user.
If we run this test, we will get the following error:
TypeError: Cannot read property ‘then’ of undefined
This makes perfect sense if we look at our implementation code. The implementation is expecting
userService.delete to return a promise, but we are simply returning null (the default behavior of
spyOn is to return null).
This is where we need to start handling the asynchronous nature of this code. I am going to inject the $q service and create a promise that we can return in our spy.
This takes care of the first part of the test - checking that the
userService is actually called with the specified user. Now we need to test the callback code - what happens when the promise is resolved.
Test the asynchronous code
In order to test the callback code we simply need to store a reference to the deferred object, and then call resolve on this object. Once we resolve the promise we also need to call
$apply on the scope to force angular to run a digest cycle.
That’s all there is to it. I find it really easy to test asynchronous code in angular - which really means no code needs to be untested.
You can find the entire example on Plunker. Happy coding.