Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

I'm Not Mocking You, Just Your AngularJS Tests

DZone's Guide to

I'm Not Mocking You, Just Your AngularJS Tests

· DevOps Zone
Free Resource

Download “The DevOps Journey - From Waterfall to Continuous Delivery” to learn learn about the importance of integrating automated testing into the DevOps workflow, brought to you in partnership with Sauce Labs.

You could alternatively call this post, “Avoid redundancy in your Angular unit tests.”

There are myriad approaches to implementing service calls in Angular. Some developers use the $resource service for pure REST endpoints. I prefer to isolate web interactions in a component of their own that returns a promise. Although $http returns a promise, you have to know how to parse the pieces, such as looking at the data property instead of the result itself, so I prefer to wrap it.

A service in my apps ends up looking something like this:

var blogExample;
(function (blogExample) {
     "use strict";
     var app = blogModule.getModule();
     function service(baseUrl, $http, $q) {        this.url = baseUrl + "api/examples";        this.$q = $q;        this.$http = $http;    }
     angular.extend(service.prototype, {
         getStuff: function() {            var defer = this.$q.defer();            this.$http.get(this.url).then(function(result) {                defer.resolve(result.data);            }, function(err) {                defer.reject(err);            });            return defer.promise;        }    });
     app.service("blogExampleSvc", ["baseUrl", "$http", "$q", service]);

})(blogExample || (blogExample = {}));

If you are using TypeScript you can further give it an interface, like this:

module BlogExample {    interface IExampleService {        getStuff: () => ng.IPromise<any>;    }
} 

Now if you’ve been working with Angular for any time you know you can write tests by pulling in ngMock and setting up the $httpBackend service. I might test that the service responds correctly to a successful request like this (using Jasmine):

(function () {    "use strict";
     var exampleService,        httpBackend;
     describe("exampleService", function () {
         beforeEach(function () {
             module("blogExample", function ($provide) {                $provide.constant("baseUrl", http://unittest/);            });        });
         beforeEach(inject(function ($httpBackend, blogExampleSvc) {            httpBackend = $httpBackend;            exampleService = blogExampleSvc;        }));
         afterEach(function () {            httpBackend.verifyNoOutstandingExpectation();            httpBackend.verifyNoOutstandingRequest();        });
         it("is registered with the module.", function () {            expect(exampleService).not.toBeNull();        });
         it("should set the proper URL for the service", function() {            expect(exampleService.url).toBe(http://unittest/api/examples);        });
         describe("getStuff",function () {            it("should return stuff upon successful call", function () {                exampleService.getStuff()                    .then(function (result) {                        expect(result).not.toBeNull();                    }, function () {                        expect(false).toBe(true);                    });                httpBackend.expectGET(exampleService.url).respond(200, []);                httpBackend.flush();            });        });    });
})();

(You’ll want to check for more than just not null on the call, but I’m trying to keep it simple.)

Inevitably that service will get pulled into a controller. Ironically, this is when the dynamic nature of JavaScript makes it easier to test things, but I still see people configuring the HTTP (test, mock) backend for their controller tests! Why?

Remember, unit tests should be simple, fast, and easy to write. When you test the service, you are testing that the service does its job. It should make the calls it needs to make and deal with the response codes. When you test the controller, you should assume the service has passed its tests. You should only be concerned about how the controller interacts with the service, not how the service interacts with the backend. That’s redundant!

So in order to deal with a controller that depends on your service, you should mock the service.

There are several ways to do this. The simplest is to just create your own mock on the fly. JavaScript is really, really good at this. Here’s an example where I create the mock directly and use it to count calls. Notice that in the module configuration, I override the “real” service definition with my mock one, and then use the injector to wire up the promise service. I also define a handy function named flushPromises that triggers digest so promises are satisfied.

(function() {    "use strict";
     var controllerSvc,        exampleController,        exampleSvcMock,        flushPromises;
     describe("exampleCtrl", function() {
         beforeEach(function() {
             exampleSvcMock = {                count: 0,                $q: null,                getStuff: function() {                    var defer = this.$q.defer();                    defer.resolve([]);                    this.count += 1;                    return defer.promise;                }            }
             module("blogExample", function($provide) {                $provide.constant("baseUrl", "http://unittest/");                $provide.value("blogExampleSvc", exampleSvcMock);            });        });
         beforeEach(inject(function($controller, $q, $rootScope) {            exampleSvcMock.$q = $q;            controllerSvc = $controller;            flushPromises = function() {                $rootScope.$apply();            };        }));    });
});

If I were using TypeScript I would define my mock as the IExampleService type to ensure I am satisfying any requirements of the contract. Now, instead of setting backend expectations, I simply query my mock instead:

describe("refresh", function () {    it("calls getStuff", function () {        var count;        exampleController = controllerSvc("exampleCtrl");        count = exampleSvcMock.count;        exampleController.refresh();        flushPromises();        expect(exampleSvcMock.count).toBeGreaterThan(count);    });
});

It’s as simple as that! Now I’m mocking my tests instead of them mocking me. If you have a more complex component to mock, you can use built-in helpers. For example, Jasmine ships with its own spies for this purpose.

The bottom line is to avoid complexity (when a component calls a component calls a component) also isolate your components so they each have one responsibility, then mock your dependencies. You may even grow to enjoy testing more!

What are your thoughts on testing?

Discover how to optimize your DevOps workflows with our cloud-based automated testing infrastructure, brought to you in partnership with Sauce Labs

Topics:
java ,devops ,javascript ,web dev ,testing ,angularjs

Published at DZone with permission of Jeremy Likness, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}