Tuesday, April 28, 2015

I’m Not Mocking You, Just Your AngularJS Tests

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) {
            }, function(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,
    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 () {
        it("is registered with the module.", function () {
        it("should set the proper URL for the service", function() {
        describe("getStuff",function () {
            it("should return stuff upon successful call", function () {
                    .then(function (result) {
                    }, function () {
                httpBackend.expectGET(exampleService.url).respond(200, []);
    }); })();

(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 test that the service does it’s 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,
    describe("exampleCtrl", function() {
        beforeEach(function() {
            exampleSvcMock = {
                count: 0,
                $q: null,
                getStuff: function() {
                    var defer = this.$q.defer();
                    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() {
    }); });

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;
    }); });

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?


Tuesday, April 7, 2015

Single Page Applications (SPA): Your Browser is the OS

Last week I presented a webinar that covered the history of apps in the browser leading to the modern trend of SPA apps. I discussed the challenges encountered with traditional "post back" web applications, the features of Single Page Applications, and covered in depth various frameworks such as AngularJS, KnockoutJS, BackboneJS, EmberJS, and more that were written to facilitate SPA development.

Click or tap here to view the video of the presentation. Scroll to the bottom for the embedded video.

You can also view the deck here:

I hope you enjoy and learn from this presentation. Please share your thoughts, feedback, and suggestions below.

Jeremy Likness