Question

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?

Was it helpful?

Solution

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()
});

OTHER TIPS

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"
]

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();
  });

});

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>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top