Using Browserify To Enhance Your AngularJS Tests

Adam Klein

Why

  • Reusing code between Protractor and unit-tests
  • Using transpilers, like Babel, to allow ES6 code inside tests
  • Using node modules, like faker and factory-girl

Problem: Protractor & Jasmine require different API

Say you have defined a mock for your user object:

// user.mock.js
function UserMock() {  
  this.name = 'john doe';
  this.role = 'admin';
}

In order to use it in your unit tests, you would normally inject it as a factory on a mock module:

// user.mock.js
...
angular.module('mocks')  
  .factory('UserMock', UserMock);


// user.spec.js
...
var UserMock;  
beforeEach(module('mocks'));  
beforeEach(inject(function(_UserMock_) {  
  UserMock = _UserMock_;
});

Using the mock in Jasmine

In case you don’t understand how to share resources in Angular tests by using dependency injection, here is a short explanation:

  • Define a module (using angular.module)
  • Define a service / factory on it
  • Include the module using ngMock’s global module function (which is short for angular.mock.module)
  • Inject the service or factory that you want to use

Using the same mock in a Protractor test is much easier, but different.

Since Protractor tests run with Node.js and not in the browser, you have to define and use the mock as a Node module:

// user.mock.js
...
module.exports = UserMock;


// login.scenario.js
var UserMock = require('../mocks/user.mock.js');  
...

Using the mock in Protractor

Much simpler. Wouldn’t it be nice if we could use the same method for unit tests?

Solution

Using karma-browserify, we can define and use our mock using the node syntax both in our unit and Protractor tests.

First, install the plugin:

npm install karma-browserify --save-dev

Change your karma.conf.js as follows:

frameworks: ['jasmine', 'browserify'],
preprocessors: {
  '{specs,mocks}/**/**.browserify.js': ['browserify']
},
browserify: {
  debug: true
}

Then apply .browserify.js suffix to specs and mocks that use module.exports and require statements. The reason we do this is so that we precompile only these specs using browserify and not all specs and mocks.

Note: change the {specs, mock} to wherever your test files reside

The debug flag will help us debug the tests using source maps.

Now you can use the mock in your unit tests the same way you used it in Protractor:

// user.mock.browserify.js
...
module.exports = UserMock;


// user.spec.browserify.js
UserMock = require('../mocks/user.mock.js');  
...

Using the mock the same way

Using ES6

You can now easily use whatever transformers and plugins that Browserify supports. For example, for writing your tests using ES6 (including ES6 module system):

npm install --save-dev babelify

// karma.conf.js
  browserify: {
    transform: ['babelify']
  }

And then use ES6 inside your mocks / tests:

// user.mock.js
  class UserMock {
    ...
  }
  export default UserMock;

  // user.spec.js
  import UserMock from '../mocks/user.mock.js';

Using node modules in unit tests

There are Node modules that are very helpful for tests & mocks, such as faker. Using them in unit tests is easy, now that we have configured Browserify:

// user.mock.js
  var faker = require('faker');
  class UserMock() {
    constructor() {
      this.first_name = faker.name.firstName();
      this.role = 'admin';
    }
  }

Requiring Node modules from inside a test

Browserify is agnostic to the module system you’re using. You can use Node’s module.exports with ES6’s import, and vice versa — use ES6’s export with Node’s require:

// these work together:
  module.exports = UserMock;
  import UserMock from '../mocks/user.mock.js';

  // And so do these:
  export default UserMock;
  var UserMock = require('../mocks/user.mock.js');

  // It even works with node modules:
  import faker from 'faker';

Browserify is agnostic to the module system

Happy testing :)

Popular