Angular services serve as a way for us to store data that will be shared through our various Angular components. Services act as a single-point of truth for our application’s data, and as such they are often reused, sometimes heavily. Ensuring services are well-tested is a crucial part of maintaining a healthy Angular app.
Along with acting as a data store, Angular components are singletons, which make them great candidates for housing shared code. In particular, services are a great interface between the Angular app and the backend, so, hopefully, your services are using Angular’s
$http service to do this communication. Mocking out the response from
$http will be necessary to testing many services.
Let’s build our spec using the lessons we learned last time. We’ll start with a
1 2 3 4 5
Here is our Player service. Note that we are using a factory under the hood. The differences between factories and services are minimal, so it’s usually best to pick one for a project and stick with it throughout.
1 2 3 4 5 6 7
And here’s our spec for the Player service. Recalling a lesson from last time, we initialize the
PlayerService at the top of the spec. In the
beforeEach block, we initialize the
core module, where our
PlayerService lives. And then, because we intend on calling the service outside of this
beforeEach block, we make sure we inject it using the underscore notation.
Let’s add a spec for the
1 2 3 4 5 6 7 8 9 10 11
Not too bad. Services are perfect candidates for unit testing. Every public method in a service can and should have a corresponding test.
In this instance, we don’t need any further setup to test this method. We can make the call directly in the first argument of the
expect call. Because the method returns an array, we need to use the Chai method
eq can be used for primitives; it checks for direct equality.
eql iterates through the items being compared and checks for deep equality. For this reason,
eql must be used for checking equality of objects and arrays.
Now let’s see an example where we need to test some more logic.
1 2 3 4 5 6 7 8
#getTypes checks on the value of an external service,
currentUser, before returning a value. Let’s see how to account for that external service and the extra conditional.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
Alright, our spec has blown out a bit. We are injecting two more services. The first makes sense: we need to set values on
currentUser to fully test out
#getTypes. The second is a bit less intuitive: We inject
$rootScope so we can create a scope for our test set up. We’ll need this scope to run a digest cycle to update the state for our assertion.
We still have a single describe block for our single function, but now, we test out both forks of the conditional in our
#getTypes method. So, we need test set up for each block. We set
isAdmin on the external service before each expectation. After any sort of test set up, we need to digest the scope to propagate the change. And our method is fully tested!
$http and $httpBackend
What will the service look like when we have an external service? We need to add the
$http service to make a server-side request.
1 2 3 4 5 6 7 8
In this example, we’re making a GET request. The
get functions makes that request and returns a promise. The first argument in that promise is run on a successful request; the second is run on an unsuccessful one.
ngMock comes with
$httpBackend: a mocked out
$http service. Before we take a look at any expectations, let’s take a look at the test set up.
1 2 3 4 5 6 7 8 9 10 11 12
We’ll be using $httpBackend throughout the spec, so we start by initializing it at the top and injecting it with the underscore syntax. Then, we immediately set up an
afterEach block. Calling
#verifyNoOutstandingRequest ensures that the expectations set up are here are torn down correctly. Without these calls, it’s possible that the tests here could step on subsequent tests and cause them to fail. These test failures can be hard to hunt down and fix, so tearing down the
$httpBackend expectations are imperative.
Let’s look at a spec for the successful case next.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Let’s walk through this one line by line starting with the test set up for
#findAll. We start by calling
$httpBackend.expectGET which sets up an expectation that the url passed in will be called. Next, we make the call to the method we are testing. Finally, in the
it block, we call
flush. This resolves all
$http calls. With this flush, all of our expectations are met, and the test passes.
And our spec testing errors:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
$httpBackend has two different flavors of mocks. We saw the first in the success case: expect. The
expect methods stub out requests that need to be called for the test to pass. In the error case we see the other flavor: when. The
when methods are more forgiving. These requests don’t need to be called, but when we flush out the
$http service, we can set up the responses that will be returned. In fact, we’ll get a “No pending requests to flush” error if we try to flush our requests that have not yet been set up. In this instance, we want to mock out an error, so we tell the service to return a 422 when it’s flushed out. The second argument is the data response itself. The
$log service holds the logs in the
debug object which is set up perfectly for testing.
Services are shared components that can be used throughout your application. Because so much of your application depends on them, services should be tested thoroughly. If you are communicating with a server, you should be setting up those HTTP calls with Angular’s
$httpBackend is set up to mock out these backend responses. Hopefully, these patterns and tips should give you a strong foundation for keeping all of your service tests strong.