DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations

Trending

  • The Native Way To Configure Path Aliases in Frontend Projects
  • What Is JHipster?
  • Multi-Stream Joins With SQL
  • [DZone Survey] Share Your Expertise for Our Database Research, 2023 Edition
  1. DZone
  2. Coding
  3. Frameworks
  4. I'm Not Mocking You, Just Your AngularJS Tests

I'm Not Mocking You, Just Your AngularJS Tests

Jeremy Likness user avatar by
Jeremy Likness
·
May. 01, 15 · Interview
Like (1)
Save
Tweet
Share
16.32K Views

Join the DZone community and get the full member experience.

Join For Free

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?

unit test AngularJS Web Service

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

Opinions expressed by DZone contributors are their own.

Trending

  • The Native Way To Configure Path Aliases in Frontend Projects
  • What Is JHipster?
  • Multi-Stream Joins With SQL
  • [DZone Survey] Share Your Expertise for Our Database Research, 2023 Edition

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com

Let's be friends: