19

I have been learning AngularJS and things have been going pretty smoothly regarding unit testing, but I've reached a bit of a tricky spot.

Suppose I have a simple form, for example:

<form name="form">
    <input type="text" name="number" ng-pattern="/^d+$/">
</form>

If I was testing something like a controller, I know that I would write it something like this (using Jasmine + Karma):

beforeEach(module('some.module'));

beforeEach(inject(/* services */) {
    /* inject necessary services */
});

it('should be invalid when given bad input', function () {
    form.number = 'Not a number';
    expect(form.number.$valid).toBeFalsy();
    expect(form.$valid).toBeFalsy();
});

But I don't know which services I need to inject, and I haven't had any luck finding documentation on unit testing in either the forms guide or the ng-form documentation.

How does one unit test a form in Angular?

user1338062
  • 11,939
  • 3
  • 73
  • 67
NT3RP
  • 15,262
  • 9
  • 61
  • 97
  • have you tried injecting the controller which holds the form logic? – bencripps Jul 29 '14 at 18:41
  • 1
    Right now, the form validation is handled by plain angular directives like `ng-pattern`. If I do inject the controller into my test, it doesn't appear to hold any reference to `form` (nor does the scope). – NT3RP Jul 29 '14 at 18:48
  • 1
    You do not unit test the form but the controller that handles it. To test the form if it matches the ng-pattern and other stuff like that use e2e testing. – Adrian Neatu Jul 29 '14 at 20:15
  • 2
    Supposing that I wanted to unit test the controller that handles the form, how would I move the logic that is currently embedded in the form into the controller? – NT3RP Jul 29 '14 at 20:18

4 Answers4

23

I'm not convinced this is the best way to unit test something like this but with some help from this answer on testing custom angular directives and some experimentation, I figured out a way to unit test the form.

After installing karma-ng-html2js-preprocessor and configuring it, I managed to get a working unit test like this:

var scope, form;

beforeEach(function() {
  module('my-module');
  module('templates');
});

beforeEach(inject($rootScope, $controller, $templateCache, $compile) {
    scope = $rootScope.$new()

    ctrl = $controller('MyController'), {
        "$scope": scope
    }

    templateHtml = $templateCache.get('path/to/my/template.html')
    formElem = angular.element("<div>" + templateHtml + "</div>")
    $compile(formElem)(scope)
    form = scope.form

    scope.$apply()
}

it('should not allow an invalid `width`', function() {
  expect(form.$valid).toBeTruthy();
  form.number.$setViewValue('BANANA');
  expect(form.number.$valid).toBeFalsy()
});
Community
  • 1
  • 1
NT3RP
  • 15,262
  • 9
  • 61
  • 97
  • This doesn't seem to work for me. The compile works fine and the controller fires up, but when changes occur in the scope and i run digest, angular throws me an error createElement("div") is not a function. – Janne Annala Nov 09 '16 at 10:08
4

I guess i can add some details to the accepted answer: karma-ng-html2js-preprocessor should be configured in the karma.conf.js file in a similar way:

//karma.conf.js
ngHtml2JsPreprocessor: { 
    moduleName: 'templates'
},
files: [
    //... other files
    //my templates 
    'app/**/*.html'
],
preprocessors: {
    'app/**/*.html': ['ng-html2js']
}, 
plugins: [
    //... other plugins
    "karma-ng-html2js-preprocessor"
]
andrea.spot.
  • 496
  • 4
  • 9
1

Here's a way to unit test with an angular form without having to compile a controller's template. Works well for me in my limited usage.

describe('Test', function() {

  var $scope, fooController;

  beforeEach(function($rootScope, $controller, formDirective) {

    $scope = $rootScope.$new();
    fooController = $controller('fooController', {$scope: $scope});

    // we manually create the form controller
    fooController.form = $controller(formDirective[0].controller, {
      $scope: $scope,
      $element: angular.element("<form></form>"),
      $attrs: {}
    });

  });

  it('should test something', function() {
    expect(fooController.form.$valid).toBeFalsy();
  });

});
ssmith
  • 684
  • 9
  • 11
1

Alternatively, if you are using WebPack with karma-webpack - you can include the template with require, without the need of karma-ng-html2js-preprocessor package:

describe("MyCtrl's form", function () {
    var $scope,
        MyCtrl;

    beforeEach(angular.mock.module("my.module"));

    beforeEach(inject(function (_$rootScope_, _$controller_, _$compile_) {
        $scope = _$rootScope_.$new();

        // Execute the controller's logic
        // Omit the ' as vm' suffix if you are not using controllerAs
        MyCtrl = _$controller_("MyCtrl as vm", { $scope: $scope });

        // Compile the template against our scope to populate form variables
        var html = require("./my.template.html"),
            template = angular.element(html);

        _$compile_(template)($scope);
    }));

    it('should be invalid when given bad input', function () {
        MyCtrl.form.number.$setViewValue('Not a number');
        expect(MyCtrl.form.number.$valid).toBeFalsy();
        expect(MyCtrl.form.$valid).toBeFalsy();
    });
});

HTML:

<form name="vm.form">
    <input type="text" name="number" ng-pattern="/^d+$/">
</form>
Jonas Masalskis
  • 1,206
  • 1
  • 15
  • 14