Thursday, June 27, 2013

Angular JS - Unit Testing - Services

Edit: (updated Feb 28, 2014)

I did a previous entry on unit testing controllers (it also covers the basic set up for Jasmine testing).That's probably the most important thing you can unit test, but what about services? Those are the piece you're using to do things like talk to other controllers, send AJAX requests, and/or interact with any dependency in your Angular application.

Testing services isn't exactly straight forward, at least if you're coming from the mindset of testing controllers. It can get a little more convoluted when your service has dependencies on other services. How can you test it in isolation from the other pieces?

Basic service testing

To start off, let's look at an extremely basic service. Something that just does something really minor, but you use this functionality everywhere, so you'd like it to be injectable.  Below you'll see a really simple service that just manipulates some text and returns it.

To test this, what we need to do is simply inject it with angular-mocks inject() helper function in our beforeEach() call, then we can make calls to it and test the outcomes:

Testing a service with $http using $httpBackend

Things get a little trickier when we want to test a service that depends on other services, particularly in the case of $http. Fortunately, $http relies on $httpBackend in Angular, and angular-mocks.js automatically provides a mock $httpBackend for you.

So if we were to look at a simple service that gets data over $http, it might look something like this:

The process here is fairly similar to what we did above. Since angular-mocks.js has already provided the mock $httpBackend, we can use some functions it's added to test how $http is being used, and even to mock what it returns. We simply need to inject the $httpBackend and make the appropriate calls to it's newly provided test helper functions, such as when(), expectGET() or expectPOST(). We also need to add an afterEach() call to assert that our expectations set in $httpBackend were met.:

Providing custom mocks

Finally, you're likely to have the need to provide mocks to other services in order to test your service in isolation. For example, if you have a service foo that depends on service bar, you may want to inject a mock to bar so you can test foo in isolation from bar. So presuming we have the following service, and we want to test myService in isolation from foo:

Providing mocks is as easy as adding an anonymous function to the module() call that loads your module, that set up values in it's provider. At the same point in your beforeEach() that you're loading the module you're testing, just add a second parameter that is a function that sets up your mock (replacing whatever service was there before).

That should be all you require to test your services.  Test well, test thoroughly, test red-green if you can. If you're testing something that injects $http, use $httpBackend where you can, but you can, of course, mock $http yourself if you wanted to. The world is your oyster here, as long as your tests cover your code well.

And, since I try to provide something for people to play with, here is a working plunk demonstrating the techniques I wrote about above.


  1. Hi there, you article is great it gave me some insights thanks, although I'm struggling when I try to flush $http, I posted a question on stackoverflow in case you want to see the code

  2. Thanks for these AngularJS testing articles. Something about them got me thinking about Mock-Driven Development and that has kept me up late reading this: among other things. I found it intruiging that he see "mockist" vs. "classic" approaches to TDD leading to different design desicions.

    I suppose I should keep my head on a switvevl when developing mocks, lest they unduly influence something :) Or at least I should be cognizant of the choices I make when doing using angular-mocks, because you can clearly use them as stubs or as true, behaviorally-testable tools. I also don't want to overengineer the testing at the cost of actually implementing the software!

    This is obviously a tangent from testing in angular, but you did say you wanted to learn and not teach and that's about all I have to contribute back at the moment.

    Cheers -- and Go Stillers.

  3. Great post, Ben! I've been trying to make the $provide trick work for awhile but I think I've been doing it at too high a level and the mock would stick for other tests in the suite. So I had just been going "old school" and exposing my dependent services as properties of the service under test. Now I can go clean all that up!

  4. Thanks for this article. I'm just trying to get myself set up with unit testing before my next Angular project so any info I can find is a bonus. I do have a question on this though. In my last personal project, I created an Angular service which was basically a wrapper around the Dropbox Datastore API. I'm guessing this is the Angular way to do things, but I'm also guessing I wouldn't really be able to unit test such a service as the actual Dropbox api calls will be in the service itself rather than having been injected - so no way to mock-out the Dropbox back-end component.

    I'm guessing in this situation I would just need to write tests that actually test the Dropbox API through my service instead - ie a live service test, but with mock data. Is this correct?

    1. It doesn't sound right, honestly. Any call to an external API via AJAX should be caught by $httpBackend, just make sure you're including the angular-mocks.js in your test page.

  5. Ah - thanks for that. I misunderstood the reference to $httpBackend thinking it was there exclusively for the $http service. I'll spend some time playing around with this.

  6. Hi,
    thanks for your examples. But somehow I did not get my head around your injection of the services. After reading the source and the docs in angular-mock.js, I came to folloging solution. Find it more clear:

    describe('$basicService tests', function (){

    beforeEach(function (){

    it('should have an exciteText function', inject(function($basicService) {

    it('should make text exciting', inject(function($basicService) {
    var result = basicSvc.exciteText('bar');

  7. Nice article but seriously, you need to get a better syntax highlighter!! :)

  8. Thank you so much!!!! This was the missing piece of the puzzle, how to deal with those promises. Thanks again!

  9. What if the service is returning an array of objects? toHaveBeenCalledWith doesn't seem to work even if the values appear to be the same.

    1. Things changes in 1.2.4 or 1.2.5 with angular-mocks. Now it's actually returning a copy of the data, so you need to use `toEqual()` instead of `toBe()`... I'll update the code. Thanks

  10. Thanks. This got me through my first, functional unit test :)

  11. These are still some of the only good examples of AngularJS service unit tests I could find on the web. Thanks so much.


This form allows some basic HTML. It will only create links if you wrap the URL in an anchor tag (Sorry, it's the Blogger default)