javascriptangularjstestingangular-mock

How to test angularjs component with DOM


I am trying to get familiar with testing an AngularJS application. While testing component logic is clear more or less, I have a trouble with html templates and model binding, because I'd like to test html binding together with controller's logic.

Test is run by karma in a real browser, so testing environment supports DOM.

It looks like it's not possible, doesn't it?

describe('sign-up', function () {
        angular.mock.module('myApp');
        angular.mock.inject(function($componentController, $rootScope, $document) {
            let scope = $rootScope.$new();
            let signUp = $componentController('signUp', {$scope: scope});
            console.log(`signup = ${signUp}`);
            for (let [k,v] of signUp) {
                // there is no field signUp.firstName
                // but inside the controller code referencing  
                // this.firstName is working
                console.log(`signup.${k} = ${v}`);
            }
            // jquery cannot find #firstName node
            // $('#firstName').val('dan') gets the same outcome
            $document.find('#firstName').val('dan');
            expect($document.find('#firstName').val()).toBe('dan');
            // without DOM form submission is not possible
        });
    });
});

Controller component:

angular.
    module('myApp').
    component('signUp', {
        templateUrl: template,
        controller: [
            function () {
                this.form = {};
                var self = this;
}]});

Template:

  <form novalidate name="$ctrl.form" >
    <div class="form-group">
      <input type="text" class="form-control"
             ng-model="$ctrl.firstName"
             required
             name="firstName"
             id="firstName"
             />
    </div>
  </form>

Solution

  • You can use $compile service to test your component templates ($componentController creates instances of component controllers without creating any markup, even $compile will not attach it to $document, so you have to use angular.element to check your template).

    Here is a working example for your component:

    angular.module('myApp', [])
        .component('signUp', {
            template: '<form novalidate name="$ctrl.form" >\n' +
            '    <div class="form-group">\n' +
            '      <input type="text" class="form-control"\n' +
            '             ng-model="$ctrl.firstName"\n' +
            '             required\n' +
            '             name="firstName"\n' +
            '             id="firstName"\n' +
            '             />\n' +
            '    </div>\n' +
            '  </form>',
            controller: 'SignUpController'
        })
        .controller('SignUpController', [function myComponentController() {
            var ctrl = this;
            ctrl.form = {};
        }]);
    
    /*
    TESTS GO HERE
    */
    
    describe('Testing a component controller', function() {
    
        beforeEach(module('myApp', function ($provide) {
    
        }));
    
        beforeEach(inject(function ($injector) {
    
        }));
    
        describe('with $compile', function () {
            var element;
            var scope;
            var controller;
    
            beforeEach(inject(function ($rootScope, $compile) {
                scope = $rootScope.$new();
                element = angular.element('<sign-up></sign-up>');
                element = $compile(element)(scope);
                controller = element.controller('signUp');
                console.log(element);
                scope.$apply();
            }));
    
            it('should render template', function () {
                expect(element.find('input').val()).toBe('');
                controller.firstName = 'Dan';
                scope.$apply();
                expect(element.find('input').val()).toBe('Dan');
            });
        })
        
    });
    .as-console-wrapper {
      height:0;
    }
    <!DOCTYPE html>
    <html>
    
      <head>
        <!-- jasmine -->
        <script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine.js"></script>
        <!-- jasmine's html reporting code and css -->
        <script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine-html.js"></script>
        <link href="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine.css" rel="stylesheet" />
        
        <script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/boot.js"></script>
        <!-- angular itself -->
        <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.js"></script>
        <!-- angular's testing helpers -->
        <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular-mocks.js"></script>
      </head>
    
      <body>
        <!-- bootstrap jasmine! -->
      <script>
        var jasmineEnv = jasmine.getEnv();
        
        // Tell it to add an Html Reporter
        // this will add detailed HTML-formatted results
        // for each spec ran.
        jasmineEnv.addReporter(new jasmine.HtmlReporter());
        
        // Execute the tests!
        jasmineEnv.execute();
      </script>
      </body>
    
    </html>