Is AngularJS testing mocking you? Why, yes, it is!

Mocking a request in AngularJS, the annoying version.

Let me start this article with a disclaimer: I am new to AngularJS.

That being said, I really enjoyed coding with it, and despite its opinionated nature (as some people keep saying), I found it to be fun, mostly intuitive (once I knew a bit more of the API) and it worked really well. That is, of course, until I got to the point of writing tests. 

The first few tests were easy enough. Inject the service I was testing, make sure the object existed and had everything I expected it to have. Awesome. All the tests passed after a few minor noob mistakes of mine were cleared.

Then came the async test. I read a few articles, tried a few things out and everything seemed to be working well there. A simple test for async functionality would look like this:


it('should succeed in doing a simple non related async test', function(){

        var cb = jasmine.createSpy();

        setTimeout(cb,1900);

        waitsFor(function(){return cb.callCount > 0},'should succeed',2000);

        runs(function(){expect(cb).toHaveBeenCalled();});
    });

Pretty easy and it works too. But I needed to test an ajax request so I wrote one like this: 

 

'use strict';

describe('Service: MyService', function () {
   // instantiate service
    var MyService , httpBackend, http;
    beforeEach(function(){
        module('MyApp');
        inject(function (_MyService_) {
            MyService = _MyService_;
        });
        inject(function($httpBackend, $http) {
            httpBackend = $httpBackend;
            http = $http;
            var message = {msg:"This is a message."};
            httpBackend.whenGET('/mock/message/1').respond(message);
        });
    });

    afterEach(function(){
        httpBackend.verifyNoOutstandingExpectation();
        httpBackend.verifyNoOutstandingRequest();
    });

    it('should make one call to mock service and retrieve something', function () {

        var cb = jasmine.createSpy();

        http.get("/mock/message/1").success(cb).error(cb);

        waitsFor(function(){return cb.callCount > 0},'should succeed',2000);

        runs(function(){httpBackend.flush();expect(cb).toHaveBeenCalled();});

    });
});

 

This test, according to practically every blog post out there, should have worked. It didn't.  Then came two workdays of banging my head against the desk, the wall, my own fist and various other objects. You may notice that in the tests, I am showing a test without the ngMockE2E module. I tried that too. It didn't work either. More head banging ensued.  You may also notice that I'm actually mocking a request, not trying to go to some outside url like http://www.google.com and simply testing for something to return. No, angular-mocks.js does not allow that. It fails with an error of unexpected request . There IS of course, a way around the unexpected request problem. You could do something like this:

httpBackend.whenGET('http://www.google.com').passThrough();

It allows the request to go through the regular channels and not the fake $httpBackend service. Still, you can only do it in the currently unstable versions of AngularJS or with ngMockE2E (still didn't work properly with ngMockE2E for me, but that may have been an issue with me, not the module).

So AngularJS was mocking me and testing me, instead of it being the other way around. I am nothing if not stubborn so, back to the interwebs I went. 

Eventually, I found the answer in some dark corner of the internet on a blog of some kind that I can't even find again.

The answer was simple, but essential. AngularJS is opinionated AND hungry, so to make $http requests fire in a test, you must either $apply or $digest

The test that finally worked looked like this: 

'use strict';

describe('Service: MyService', function () {
   // instantiate service
    var MyService, httpBackend, http, root;
    beforeEach(function(){
        module('MyApp');
        inject(function (_MyService_) {
            MyService= _MyService_;
        });
        inject(function($httpBackend, $http, $rootScope) {
            httpBackend = $httpBackend;
            http = $http;
            root = $rootScope;
            var message = {msg:"This is a message."};
            httpBackend.whenGET('/mock/message/1').respond(message);
        });
    });

    afterEach(function(){
        httpBackend.verifyNoOutstandingExpectation();
        httpBackend.verifyNoOutstandingRequest();
    });    

    it('should make one call to mock service and retrieve something', function () {

        var cb = jasmine.createSpy();

        http.get("/mock/message/1").success(cb);

        waitsFor(function(){return cb.callCount > 0},'should succeed',2000);
        root.$apply();
        httpBackend.flush();
        runs(function(){expect(cb).toHaveBeenCalled();});
    });
});

This worked for me, and with a few minor tweaks, managed to work with the actual service I was testing. Your own exprience may vary, and you may want to put the $apply and flush calls in the runs() callback, but as it is written above should work for a simple $http test.

 

Thank you for your interest!

We will contact you as soon as possible.

Send us a message

Oops, something went wrong
Please try again or contact us by email at info@tikalk.com