How to test angularjs component with DOM
Asked Answered
G

2

5

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>
Greenbrier answered 15/1, 2018 at 9:37 Comment(3)
You have to take a look at $compile service. $componentController will not help you in testing your component templates. Take a look at my answer here. The first example shows how to test template (called "should render template").Langton
The referenced answer calls "signUp.$onInit()". My setup looks the same, while my component doesn't have $onInit().Greenbrier
Check the answers, I've setup an example, probably it will be more clear then my first comment :)Langton
L
4

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>
Langton answered 15/1, 2018 at 10:58 Comment(2)
it works with inlined html. TemplateUrl requires extra workaround.Greenbrier
Yeah, inline html template was chosen for the simplicity of the example. Template url could require using 3rd party tool (like npmjs.com/package/gulp-templatecache) to put the external html templates into $templatecache and use it in your tests.Langton
T
2

You need create and compile element:

let element = angular.element('<sign-up></sign-up>');
const $compile = $injector.get('$compile');
element = $compile(element)($scope);

controller = element.controller('signUp');

P.S. $document.find('#firstName') wont return anything since you dont add this element anywehere. Use element.find instead

Tiertza answered 15/1, 2018 at 10:56 Comment(1)
in my case element.find doesn't support search by id while search by tag name is workingGreenbrier

© 2022 - 2024 — McMap. All rights reserved.