Good Unit Testing for Angular Services

Writing AngularJS services to be fully unit testable, while not exposing all their guts, is not immediately intuitive. A year or two ago, as part of my work at Udacity, I came up with this method, and it remains my favorite because it:

  1. The service only exposes methods we want to be public. People using your service can’t get at the private configurations and internal methods. Everything stays safe!
  2. Lets us test, stub out, alter, or otherwise muck with those internal methods, but only during testing.

What’s the secret sauce? Two services — one meant to be the injected service, and one used only in a support role.

An example is probably due here, so let’s take a service we use for logging in via a google account.

Here’s the entire code we’ll talk about.

As you can see browsing through that file, there is a service called google and one called googleHelper. When we are injecting google into our codebase, we’re really only interested in the getToken() and getMeResponse() methods.

When we’re testing, that’s not everything we’re interested in.

To be responsible angularsmiths, we shouldn’t expose our connection configurations, our init methods, etc. These are all pieces of this service that a user of this service should not have access to because it means they can break it for everybody else.

So we have this Catch 22, where we need to expose these pieces when we go to unit test, but we absolutely do not want to expose them when our code is being used.

This is where the two-service method shines. We can mock out googleHelper when we test google. We can have googleHelper.getConfig() return whatever we want. We can take control of googleHelper.loadApi() and determine when we resolve that promise. We can have a bunch of undocumented methods that are exposed, because we’re the ONLY people who will use them.

All well and good, but let’s see those tests!

As far as files format is concerned, you’ll notice that, in the same way we have google and googleHelper in the same file, their tests also share a file. From a mental model standpoint, these two services are one entity, so we keep them together.

You can paw through that test file (you’ll notice we’ve added a bit of sugar to our testing language) but I’ll point out a few pieces that show the strength of this solution:

  • At the top of the test file we stub out all the guts of googleHelper, and add our own test data in there. Now we can test our google service by itself with whatever test configurations we want.
  • We can access, stub out, do whatever we want with googleHelper and none of those properties or methods are exposed on the google service. Our code is fully accessible, but only during testing. In the field, google only exposes its safe methods.

 

This way of setting up your code to be testable has meant all of our services only expose what they should for development work, but are fully unit tested as well. Yay for 100% coverage and code you can’t hurt yourself with!

Leave a Reply

Your email address will not be published. Required fields are marked *