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.
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:
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.
$q.then $q.catch $q.finally
This is how
$http usually works:
However you can use $q's standard promises which Angular's
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.expectPOST('/api/path', postData, (headers) -> return headers['Content-Type'] == 'application/json;charset=utf-8' ).respond(200, returnData)
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:
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:
Finally we can assert or expect:
In order to test if the
.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)
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.
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.
In order to follow the "Don't repeat yourself" principle, make sure to employe
afterEach to declare re-occuring spies, stubs, mocks or fake servers.
Full Code Example
Please note that I substituted above
/user/signIn for better clarity of what I am trying to achieve (psst: testing Authentication).
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.