ngImprovedTesting: mock testing for AngularJS made easy

NOTE: Just released version 0.1.3. More info can be found in the README of the GitHub repo.

Being able to easily test your application is one of the most powerful features that AngularJS offers. All the services, controllers, filters even directives you develop can be fully (unit) tested.

However the learning curve for writing (proper) unit tests tends to be quite steep.
This is mainly because AngularJS doesn’t really offer any high level API’s to ease the unit testing. Instead you are forced to use the same (low level) services that AngularJS uses internally. That means you have to gain in dept knowledge about the internals of $controller, when to $digest and how to use $provide in order to mock these services. Especially mocking out a dependency of controller, filter or another service is too cumbersome.

This blog will show how you would normally create mocks in AngularJS, why its troublesome and finally introduces the new ngImprovedTesting library that makes mock testing much easier.

Sample application

Consider the following application consisting of the “userService” and the “permissionService”:

When it comes to unit testing “permissionService” there are two default strategies:

  • using mock $httpBackend (from the ngMock module) to simulate $http trafic from the “userService”
  • using a mock instead of the actual “userService” dependency

Replacing the “userService” with a mock using vanilla AngularJS

Using vanilla AngularJS you have to do all the hard work yourself when you like to create a mock.
You will have to manually create an object with its relevant fields and methods.
Finally you will have to register the mock (using $provide) to overwrite the existing service implementation.

Using the following vanilla AngularJS we can replace “userService” with a mock in our unit tests:

The imperfections of the vanilla style of mocking

To ability to mock services in unit tests is a really great feature in AngularJS but it’s far from perfect.

As a developer I really don’t want to be bothered with having to manually create a mock object.
For instance I might just simply forget to mock the “userService” dependency when testing the “permissionService” meaning I would accidentally test it using the actual “userService”.
And what if you would refactor the “userService” and would rename its method to “getUserInfo”.
Then you would except the unit test of “permissionService” to fail, right?
But it won’t since the mocked “userService” still has the old “getUserDetails” (spy) method.

Make things even worse… what if you would rename service to “userInfoService”.
This makes the “userService” dependency of the “permissionService” to be no longer resolvable.
Due to this modification the application will no longer bootstrap when executed inside a browser.
But when executed from the unit test it won’t fail since its still uses its own mock.
However other unit tests using the same module but not mocking the service will fail.

How mock testing could be improved

Coming from a Java background if found the manual creation of mocks felt quite weird to me. In static languages the existence of interfaces (and classes) make it way more easy to automatically create mocks.

Using AngularJS we could do something similar …
… what if we would use the original service as a template for creating a mocked version.

Then we could automatically create mocks that contain the same properties as the original object. Each non-method property could be copied as-is and each method would instead be a Jasmine spy.

Instead of manually registering a mock service using $provide we could instead automate this. This would also allow us to automatically check if a service you want to mock actually exists. Also we could check if the service being mock is indeed being used as dependency of a component.

Introducing the ngImprovedTesting library

With the intention of making (unit) testing more easy I created the “ngImprovedTesting” library. The just released 0.1 version supports (selectively) mocking out dependencies of a controller, filter or another service.

Mock out the “userService” dependency when testing the “permissionService” is now extremely easy:

Instead of using the traditional “beforeEach(module(‘myApp’))” we are using the ModuleBuilder of “ngImprovedTesting” to build a module specifically for our test.
In this case we would like to test the actual “permissionService” in a test in combination with a mock for its “userService” dependency.

But what if I would like to set some behavior on the automatically created mock …
… how do I actually get a hold on the actual mock instance?

Well simple… besides the component being tested all its dependencies including the mocked one can be injected.

To differentiate a mock from a regular one it’s registered with “Mock” appended in its name. So to inject the mocked out version of “userService” just use “userServiceMock” instead:

As you can see in the example the “userServiceMock.getUserDetails” method is a just a Jasmine spy. It therefor allows invocation of “andReturn” on in order to set the return value of the method. However it does not allow an “andCallThrough” as the spy is not on the original service.

NOTE:

  • ngImprovedTesting will only mock out services that are either a function or an object with at least one (inherited) method (another than from Object.prototype); so for instance a ‘constant’ service named “APPCONFIG” with value {url: ‘http://hostname/some/path’} will not be mocked and therefor be available through APPCONFIG (and not APPCONFIGMock) in your Jasmine specs.
  • ngImprovedTesting uses an undocumented (internal) API of AngularJS to find out which dependencies a module component (i.e. controller or another service) has. This undocumented API remains unchanged since Angular 1.0 and still is unchanged (at the time of writing) in the lastest AngularJS 1.3. To ensure that the undocumented API remains working as expected I created a huge Jasmine test. As part of the Grunt build these tests are run against AngularJS 1.0.8, the latest stable AngularJS 1.2 version as well as the latest nightly build of AngularJS 1.3. So in case anything does change in a new AngularJS version it should easy detectable. Additionally the learning tests are also available in Plunker, this way you can also check yourself if ngImprovedTesting is still compatible with a particular AngularJS version.

Exploring the ModuleBuilder API of ngImprovedTesting

Since I didn’t get round to writing and generating JSDocs / NGDocs, I instead will quickly explain it here.

To instantiate a “ModuleBuilder” use its static “forModule” method.
The “ModuleBuilder” (in version 0.1) consists of the following instance methods:

  • serviceWithMocksFor: registers a service for testing and mock specified dependencies
  • serviceWithMocks: registers a service for testing and mock all dependencies
  • serviceWithMocksExcept: registers a service for testing and mock dependencies except the specified
  • controllerWithMocksFor: registers a controller for testing and mock specified dependencies
  • controllerWithMocks: registers a controller for testing and mock all dependencies
  • controllerWithMocksExcept: registers a controller for testing and mock dependencies except the specified
  • controllerAsIs: registers a controller so that it can be instantiated through $controller
  • filterWithMocksFor: registers a filter for testing and mock specified dependencies
  • filterWithMocks: registers a filter for testing and mock all dependencies
  • filterWithMocksExcept: registers a filter for testing and mock dependencies except the specified
  • filterAsIs: registers a filter so that is can be using through $filter
  • directiveWithMocksFor: registers a directive for testing and mock specified dependencies
  • directiveWithMocks: registers a directive for testing and mock all dependencies
  • directiveWithMocksExcept: registers a directive for testing and mock dependencies except the specified
  • directiveAsIs: registers a directive so that is can be using through $compile
  • animationWithMocksFor: registers a animation for testing and mock specified dependencies
  • animationWithMocks: registers a animation for testing and mock all dependencies
  • animationWithMocksExcept: registers a animation for testing and mock dependencies except the specified
  • animationAsIs: registers a animation so that is can be using through $animate

Limitations in the current version (0.1.2) of ngImprovedTesting

Although version 0.1.2 is quite production ready (and well unit tested) is has a limitation:

  • Services registered with the “provider” method currently cannot be used as to be tested service; meaning it cannot be used as first parameter of “serviceWithMocks…”, however it can be used as a (potentially mocked) dependency.

How to get started with ngImprovedTesting

All sources from this blog post can be found as part of a sample application:

The sample applications demonstrates three different flavors of testing:

  • One that uses the $httpBackend
  • Another using vanilla mocking support
  • And one using ngImprovedTesting

To execute the tests on the command-line use the following commands (requires NodeJS, NPM, Bower and Grunt to be installed):

The actual sources of ngImprovedTesting itself are also hosted on GitHub:

Furthermore ngImprovedTesting is also available through bower itself.
You can easily install and add it to an existing project using the following command:

Your feedback is more than welcome

My goal for ngImprovedTesting is to ease mock testing in your AngularJS unit tests.
I’m very interested in your feedback… is ngImprovedTesting any useful… and how could it be improved?

13 thoughts on “ngImprovedTesting: mock testing for AngularJS made easy

  1. Hi Emil,

    We are using ng-improved-testing for our project.Currently i am unable use the ng-improved-testing if the angular service has angular constants as one of it dependencies.
    I am get error saying
    Error: [$injector:unpr] Unknown provider: APPCONFIGProvider <- APPCONFIG

    Below is the code

    var app = ng.module('App');
    app.constant('APPCONFIG', data);

    serviceA.js
    angular.module("App").service("ServiceA",function($http,APPCONFIG,ServiceB) {
    this.method=function(id){
    var url=APPCONFIG.url;
    var servB=new ServiceB();
    }
    });

    can we Mock angular constant using ng-improved-testing??can we use vanilia unti test with ng-improved-testing like mock APPCONFIG separately and then mock other dependencies using ng-improved-testing??if so ,do you got any sample examples to look at.

    Thank you.

    Regards,
    Srivani

    • Hi Srivani,

      Currently ngImprovedTesting will only create a mock for services which are either a function (like $resource) or an object with at least one (inherited) method.
      So APPCONFIG will never actually be mocked since its doesn’t seem to have any method (besides the one inherited from Object.prototype).

      But looking at your code I wonder if the problem is actually with ngImprovedTesting?
      I look like the file containing the “app.constant(‘APPCONFIG’, data);’ isn’t loaded before “serviceA.js” is being loaded?

      PS. I will update the blog post with a comment describing which kind of services will be mocked by ngImprovedTested and which will remain as-is.

      Kind regards,

      Emil

      Since the APPCONFIG services isn’t a function or an object with methods, ngImprovedTest will not create a mock for you.

      • Hi Emil,

        Thank you for your reply.

        yes my file with app.constant loaded before serviceA,js file and i used it as dependency.

        below code of ng-improved-testing is throwing the unknown provider APPCONFIG error.

        beforeEach(ModuleBuilder.forModule(‘App’)
        .serviceWithMocksFor(‘ServiceA’, ‘ServiceB’)
        .build());

        is there way to add my mock to above code??i have created mock for APPCONFIG in test code and is there a way i can inject this mock to above code???

        Regards,
        Srivani

  2. Hi, I have a service with a dependency on $location. When I create a test for this service I get “Error: [$injector:unpr] Unknown provider: $rootElementProvider <- $rootElement <- $location". Here is my code:

    describe('test for AppContext', function () {

    beforeEach(ModuleBuilder.forModule('csApp.factories')
    .serviceWithMocksFor('AppContext')
    .build());
    describe('baseUri', function() {
    it('should return correct url', inject(function (AppContext) {
    //var absUrl = 'http://localhost/storms2/enterprise/stormspackages/storms.app/index.html&#039;;
    //$location.absUrl.andReturn(absUrl);
    //expect(AppContext.baseUri()).toBe('http://localhost/storms2/enterprise&#039;);
    }));
    });

    });

    I commented out a bunch of stuff trying to narrow it down but basically if I don't even mention $location on the test file I still get this error because the factory has it as a dependency.

    • Jason,

      The current version of ngImprovedTesting doesn’t currently support adding additional modules like “ngMock”.
      And as it turns out “ngMock” causes the (stubbed) $location to be registered that you are currently missing.
      While working on a quick fix I also found out that ngImprovedTesting somehow didn’t create Jasmine spies for the $location methods.

      To fix it all I will create a new 0.1.3 version of ngImprovedTesting to extend it with this functionality.
      Unfortunately I will not be able to do this prior to thuesday the 26th of august.

      For now I would suggest to register an additional angular module for unit testing and manually stub out (using spyOn) the $location method:

      Hope this all helps.
      Kind regards,

      Emil

        • Jason,

          Just released 0.1.3 of ngImprovedTesting that should fix your issue.
          As it turns ngImprovedTesting didn’t include “ngMock” in its internally created $injector instance (used for resolving instances of dependencies).

          Let me know in case ngImprovedTesting 0.1.3 doesn’t solve your problem.

          Kind regards,

          Emil

    • Keith,

      No plans into moving to npmjs… to be honest I’m personally not familiar with browserify (assuming thats why you want a node package).
      But if your would like to ‘port’ it… you are more than welcome… or to for example create a pull request.

      Kind regards,

      Emil

  3. Pingback: ngImprovedTesting: mock testing for AngularJS made easy |  VishalDharmawat - iPhone and iPad Development

  4. I was wondering, is there a way to set a “Strict Mock” option in the WithMocks method(s)? Strict mocking means that we must set up expectations on all members of a mock object otherwise an exception is thrown.

    This is how Moq for .NET works and I find it really nice.

    • Currently ngImprovedTesting doesn’t offer a “Strict Mock” option.
      The current implementation merely does a “jasmine.createSpy()” for each mock property.

      I’m thinking about moving the mock creation functionality into another module that’s independent of AngularJS.
      This way it could be used in any Jasmine spec, not only for AngularJS apps.
      After that I would like to further improve the mock support (i.e. support for “.andCallThrough”) and
      possibly even add strict mocks to it.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">