Wednesday, April 8, 2015

Mild Hack for Unit Testing the Run Method of an Angular Module

In Angular 1.x, the run() method of a module behaves similar to the main method or constructor method concept found in other languages:  it's a method that runs as soon as all of the dependencies have been resovled and the module has been configured.

Because of this, even if you use run() to execute a named method that can be called separately in your unit test, the code in that method gets executed during the process of instantiating the module to use in your tests.  That makes it difficult to do any testing that compares the state of data prior to running the method or tests that require mocking dependencies and dependency behavior inside of the method.

So when I ran into this problem, I came up with a mild "hack" to work around the immediate execution of the run() method.  I made the execution of the code inside my run method dependent on the value of a Boolean constant:

angular.module( 'mainModule', [] )
    .constant( 'executeRunMethod', true )
    .run( runMethod );

function runMethod( $rootScope, authService, executeRunMethod ) {
    if ( executeRunMethod ) {
        //Execute the code as expected

runMethod.$inject = [ '$rootScope', 'authService', 'executeRunMethod' ];

With the executeRunMethod constant set to true, the runMethod() code executes via run() once the module is wired up as expected, so everything "runs" normally.

In my unit test however, I can override the executeRunMethod constant during the process of instantiating the module, setting it to false, effectively preventing the run() method from executing.

'use strict';

describe( 'mainModule', function () {
    var $rootScope,

    beforeEach( function () {

        module( 'mainModule', function ( $provide ) {
            // The executeRunMethod constant is overridden to be false
            $provide.constant( 'executeRunMethod', false );
        } );

        inject( function ( $injector ) {
            $rootScope = $injector.get( '$rootScope' );
            mockAuthService = { requestUser: function() {} };
        } );


Later in the test file, I can test the runMethod directly, passing in the needed arguments, including a "true" instance of the executeRunMethod argument allowing the code to be executed:

describe( 'the runMethod function', function() {

        var executeRunMethod = true;

        it( 'performs the expected action', function () {

            runMethod( $rootScope, mockAuthService, executeRunMethod );

            expect( result ).toEqual( expectation );

So this arrangement lets me test the code used by the module run() method just as if it was a normal method.