Testing Angular's $http module and its promises

When I started Unit Testing my Angular Services, I faced certain difficulties originating in not understanding completely how the $http module and promises in Angular worked. After some digging I figured out a testing method that works for me in plain good'ol Jasmin, without the help of any other library - even though there are excellent ones out there, e.g. sinon.js.

TDD

If you want to build big software you need to ensure software quality right from the start or you will bug hunting will become a fulltime occupation, diabling you to continue the project. This is why Test Driven Development and Unit Tests are there, making Regression Tests and even E2E Tests almost completely obsolete. Also in my opinion even small projects, yes even 5 lines of codes should be tested with 2-3 quick asserts at the end of the production code, because there is a saying:

Nobody Ever.

So stop right now, and don't write another line of production code until you have written tests for all your units, modules, methods and so forth. I suggest you follow the Test-Driven Development Lifecycle:

Where $http diverges from $q

Promises are an abstraction over statements that allow us to express ourselves synchronously with asynchronous code. They represent a execution of a one time task.

They also provide exception handling, just like normal code, you can return from a promise or you can throw.

$http promises

$http.success
$http.error

$q promises

$q.then
$q.catch
$q.finally

This is how $http usually works:

However you can use $q's standard promises which Angular's $http module

Testing $http inherited promises with Jasmine

SetUp fake Server

Our service has to call a server, but certainly not the real one, so lets set up a quick fake by using httpBackend:

  $httpBackend.expectPOST('/api/path', postData, (headers) ->
    return headers['Content-Type'] == 'application/json;charset=utf-8'
  ).respond(200, returnData)

By using expectPost rather than when('POST'..), we are employing a TDD technique called strict testing. Meaning, we strictly require the POST to be made and we strictly expect a certain header (or other properties), only then we return true. This way the fakeServer is also a test itself, which is awesome if you ask me.

Testing the promises

Now we can move on to test these promises:

$http.then
$http.catch

Let's set up the call and the promises:

myService.callServer(postData).then((returnFromPromise) ->
    result = returnFromPromise
  ).catch((returnFromPromise) ->
    result = returnFromPromise
  )

Now we Flush. Old-Schoolers do know this from php:

$httpBackend.flush()

Finally we can assert or expect:

expect(result.email).toBe(postData.email)

In order to test if the .catch or .finally promise gets called and returns as expected, you would write another test with a different fakeServer, where you return 500 instead of the 200 status code, like so:

  $httpBackend.expectPOST('/api/path', postData, (headers) ->
    return headers['Content-Type'] == 'application/json;charset=utf-8'
  ).respond(500, returnData)

Stubbing dependencies

Spies and Stubs

So now I am using these promises of $http and I can test them with Jasmine spies, which are easily transformable into stubs e.g. the following line would just "watch" if the service and the method "callServer" is called and pass the test on to the real production code.

spyOn(myService, 'callServer').and.callThrough()

To stub myService simply change to callFake(myServiceStub) and declare your stub function like so:

I simply stubbed my authentication service that implements the localStorage module. This has multiple benefits, which should be clear:

  • Isolation of Unit tests
  • Independence of underlying Browser implementation of localStorage module
  • Enhanced Debugging capabilities in case a certain Unit test throws.

DRY

In order to follow the "Don't repeat yourself" principle, make sure to employe beforeEach and afterEach to declare re-occuring spies, stubs, mocks or fake servers.

Full Code Example

Please note that I substituted above myService and /api/path with UserService and /user/signIn for better clarity of what I am trying to achieve (psst: testing Authentication).

Conclusion

Google's Angular team made it easy for us to unit test, however there are hickups with testing the $http module and also you need a profound understanding of promises. Of course there exist other techniques of testing promises, e.g. with q and its deferred method. However above test code works great for me, and its asynchronous as well. If you have better suggestions on how to Unit Test the $http module I would be happy to hear about them in the comments below.

References

Peterbe.com: To then or to success in AngularJS

StackOverflow: Using success/error/finally/catch with Promises in AngularJS