0.1.10 • Published 8 years ago

angular-test-context v0.1.10

Weekly downloads
7
License
MIT
Repository
github
Last release
8 years ago

AngularTestContext

AngularTestContext provides a simple API that implements core ngMock capabilities needed for unit testing. Based upon your needs, it can replace the need for ngMock, or it can be used in conjunction with ngMock.

This utility was originally created to make it easier to build unit tests for directives using the Page Object design pattern.

Table of Contents

Features

  • Compatible with Angular 1.2.x and beyond.
  • Can be used in other testing frameworks besides Jasmine and Mocha.
  • Compatible with CommonJS, AMD, and non-module build environments.

Installation

To install the package:

npm install angular-test-context

To require the package:

var AngularTestContext = require("angular-test-context");

Usage

Creating a Test Context

First, make available any Angular modules needed by your test. In this example, we want to test myDirective, so we create a module and add the directive to the module:

angular.module('testApp', [])
    .directive('myDirective', myDirective); 

Next, create the AngularTestContext instance. AngularTestContext needs to know which Angular modules are used by your test. The names of the Angular modules can be passed in as an array, or as comma separated strings, or both.

var testContext = new AngularTestContext('testApp');

Compiling HTML with Scope Variables

To compile the HTML to be tested, call the .compile() method with the appropriate HTML and any optional scope variables:

var scope = {
    data: {
        value: 'hello'
    }
};
var element = testContext.compile('<div my-directive="data"></div>', scope);

The .compile() method returns the Angular wrapped DOM element, which can then be inspected during your tests to validate the directive's behavior.

Changing a Scope Value

To change a scope value, modify the value, and then call the .digest() method:

scope.data.value = 'goodbye';
testContext.digest();

Alternatively, you can access the Angular scope object used to compile the HTML, using the .scope() method:

scope.data.value = 'goodbye';
testContext.scope().$digest();

Injecting A Dependency

To inject a dependency into a function, use the .inject() method:

testContext.inject(function($timeout) {
    ...
});

To inject a dependency into a constructor, use the .instantiate() method:

function MyController($timeout) {
    ...
}

var controller = testContext.instantiate(MyController);

Page Object Design Pattern

AngularTestContext's GitHub project contains a calculator directive example for writing unit tests using the Page Object design pattern and the AngularTestContext utility.

If you haven't used the Page Object design pattern for writing unit tests, I highly recommend it. It will transform your tests from being unwieldy and fragile, to being robust and easily maintained.

The main benefits are:

  • It eliminates fragile tests. If the underlying implementation of your code changes, you modify the Page Object, not your tests.
  • Tests are much easier to understand since all implementation details are encapsulated in the Page Object.
  • Tests are much easier to write since the DRY (Don't Repeat Yourself) code is encapsulated within the Page Object.
  • Since the implementation is encapsulated within the Page Object, tests have the potential to be portable across different frameworks - preserving your investment in tests. Instead of changing your tests for each framework, create a different Page Object for each framework.

Calculator Unit Tests

Here are the unit tests for the calculator directive using the CalculatorPageObject.

A couple of points to observe:

  • There are no implementation details in the tests. The tests simply execute logical calls against the calculator's Page Object.
  • You can't determine from the tests what underlying framework is used to implement the calculator.

Note: Abstracting out the underlying framework in your tests may not always be feasible or desirable, but it's a choice worth considering.

/*
 * Unit tests for the calculator directive.
 */
'use strict';

//-------------------------------------
// Module dependencies and variables
//-------------------------------------

var Calculator = require('./CalculatorPageObject');

//-------------------------------------
// Unit tests
//-------------------------------------

describe('calculator directive:', function() {

    var calc;

    beforeEach(function() {
        calc = new Calculator();
    });

    describe('adding two numbers', function() {

        it('should be 3', function() {
            calc.operand1(1);
            calc.operand2(2);
            expect(calc.add()).toBe(3);
        });

        it('should be -2', function() {
            calc.operand1(0);
            calc.operand2(-2);
            expect(calc.add()).toBe(-2);
        });
    });

    describe('button state', function() {

        it('should be disabled if only first operand set', function() {
            expect(calc.isButtonDisabled()).toBe(true);
            calc.operand1(1);
            expect(calc.isButtonDisabled()).toBe(true);
        });

        it('should be disabled if only second operand set', function() {
            expect(calc.isButtonDisabled()).toBe(true);
            calc.operand2(2);
            expect(calc.isButtonDisabled()).toBe(true);
        });

        it('should be enabled if both operands set', function() {
            expect(calc.isButtonDisabled()).toBe(true);
            calc.operand1(1);
            calc.operand2(2);
            expect(calc.isButtonDisabled()).toBe(false);
        });
    });
});

Calculator Page Object

Here is the source code for the CalculatorPageObject, which uses the AngularTestContext utility.

/*
 * Page Object for the calculator directive.
 */
'use strict';

//-------------------------------------
// Module exports
//-------------------------------------

module.exports = CalculatorPageObject;

//-------------------------------------
// Module dependencies and variables
//-------------------------------------

var AngularTestContext = require('angular-test-context');
var calculatorDirective = require('./calculatorDirective');

// Private model name.
var MODEL = '_calculatorPageObject';

//-------------------------------------
// Page Object
//-------------------------------------

/*
 * @constructor
 */
function CalculatorPageObject() {
    var m = this[MODEL] = {};

    // Add directive to module.
    angular.module('testApp', [])
        .directive('calculator', calculatorDirective);

    // Create test context.
    var testContext = new AngularTestContext('testApp');

    // Compile and find elements.
    var element = testContext.compile('<calculator></calculator>');

    var inputs = element.find('input');
    expect(inputs.length).toBe(2);

    var button = element.find('button');
    expect(button.length).toBe(1);

    var result = element.find('span span');
    expect(result.length).toBe(1);

    m.input1 = $(inputs[0]);
    m.input2 = $(inputs[1]);
    m.button = button;
    m.result = result;
}

var proto = CalculatorPageObject.prototype;

/*
 * Sets the value of the first operand.
 *
 * @param {string} value
 */
proto.operand1 = function(value) {
    var m = this[MODEL];
    m.input1.val(value).trigger('input');
};

/*
 * Sets the value of the second operand.
 *
 * @param {string} value
 */
proto.operand2 = function(value) {
    var m = this[MODEL];
    m.input2.val(value).trigger('input');
};

/*
 * Adds the two operands.
 *
 * @returns {number} The result.
 */
proto.add = function() {
    var m = this[MODEL];
    m.button.trigger('click');
    return parseInt(m.result.html());
};

/*
 * Indicates whether add button is disabled.
 *
 * @returns {boolean}
 */
proto.isButtonDisabled = function() {
    var m = this[MODEL];
    return m.button.prop('disabled');
};
0.1.10

8 years ago

0.1.9

8 years ago

0.1.8

8 years ago

0.1.7

8 years ago

0.1.6

9 years ago

0.1.5

9 years ago

0.1.4

9 years ago

0.1.3

9 years ago

0.1.2

9 years ago

0.1.1

9 years ago

0.1.0

9 years ago