# AngularJS - testing

# tools

# unit testing

# best articles

# best lib (ng-describe)

  • ng-describe
  • Avec ng-describe, quand on mock une constante utilisée pour stocker une lib externe ( cf Y240 ), mocker la constante ne suffit pas, il faut mocker chaque fonction utilisée en lui réattribuant la fonction initiale. On injecte par IIFE la vrai lib donc on réattribue au mock les fonctions utilisées. Ou pas.

Articles de l'auteur de la lib :

# intégration jasmine dans webstorm

stackoverflow - how-can-i-get-webstorm-to-recognize-jasmine-methods

stackoverflow - Selection jasmine definitely typed

# examples

# repos examples

Guidelines and patterns for unit testing AngularJS apps.

Unit and e2e testing recipes for AngularJS

GitHub repository

# tests avec $http

# tests de services

# tests de directives

# Dependencies mocking

# Testing a throw exception

it('throws exception when args are undefined or null', inject(function (HistoryLRindexService) {
  expect(function(){HistoryLRindexService.addIndexElement(undefined, 'toto');})
    .toThrowError('HistoryLRindexValueFactory.addIndexElement(key, data) error : key null or undefined');
}));
1
2
3
4
  • use anonymous function for the call

  • use toThrowError(exceptionMsg) matcher from Jasmine

  • your impl should be something like that :

if(!key) {
  throw new Error('HistoryLRindexValueFactory.addIndexElement(key, data) error : key null or undefined');
}
1
2
3

# Testing a directive

(function () {
    'use strict';

    angular
        .module('app')
        .directive('exampleDirective', exampleDirective);

    function exampleDirective() {
        return {
            restrict: 'E',
            scope: {
                stroke: "@",
                fill: "@"
            },
            template: '<svg ng-attr-height="{{values.canvas}}" ng-attr-width="{{values.canvas}}" class="gray">' +
            '<circle ng-attr-cx="{{values.center}}" ng-attr-cy="{{values.center}}"' +
            'ng-attr-r="{{values.radius}}" stroke="{{stroke}}"' +
            'stroke-width="3" fill="{{fill}}" />' +
            '</svg>',
            link: function(scope, element, attrs) {
                var calculateValues = function(size) {
                    var canvasSize = size * 2.5;

                    scope.values = {
                        canvas: canvasSize,
                        radius: size,
                        center: canvasSize / 2
                    };
                };

                attrs.$observe('size', function(newSize) {
                    calculateValues(parseInt(newSize, 10));
                });
            }
        };
    }

}());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
(function () {
    'use strict';

    describe('directive: example', function() {
        var element, scope;

        beforeEach(module('app'));

        beforeEach(inject(function($rootScope, $compile) {
            scope = $rootScope.$new();

            element =
                '<example-directive size="{{size}}" stroke="black" fill="blue"></example-directive>';

            scope.size = 100;

            element = $compile(element)(scope);
            scope.$digest();
        }));

        describe('with the first given value', function() {
            it("should compute the size to create other values", function() {
                var isolated = element.isolateScope();
                expect(isolated.values.canvas).toBe(250);
                expect(isolated.values.center).toBe(125);
                expect(isolated.values.radius).toBe(100);
            });

            it("should contain a svg tag with proper size", function() {
                expect(element.find('svg').attr('height')).toBe('250');
                expect(element.find('svg').attr('width')).toBe('250');
            });

            it("should contain a circle with proper attributes", function() {
                expect(element.find('circle').attr('cx')).toBe('125');
                expect(element.find('circle').attr('cy')).toBe('125');
                expect(element.find('circle').attr('r')).toBe('100');
                expect(element.find('circle').attr('stroke')).toBe('black');
                expect(element.find('circle').attr('fill')).toBe('blue');
            });
        });

        describe('when changing the initial value to a different one', function() {

            beforeEach(function() {
                scope.size = 160;
                scope.$digest();
            });

            it("should compute the size to create other values", function() {
                var isolated = element.isolateScope();
                expect(isolated.values.canvas).toBe(400);
                expect(isolated.values.center).toBe(200);
                expect(isolated.values.radius).toBe(160);
            });

            it("should contain a svg tag with proper size", function() {
                expect(element.find('svg').attr('height')).toBe('400');
                expect(element.find('svg').attr('width')).toBe('400');
            });

            it("should contain a circle with proper attributes", function() {
                expect(element.find('circle').attr('cx')).toBe('200');
                expect(element.find('circle').attr('cy')).toBe('200');
                expect(element.find('circle').attr('r')).toBe('160');
                expect(element.find('circle').attr('stroke')).toBe('black');
                expect(element.find('circle').attr('fill')).toBe('blue');
            });
        });

    });

}());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

# e2e testing

# installation des drivers pour webdrivers

ATTENTION CHROME & FIREFOX, need dl update de webdriver.

Derrière un proxy il faut config envvar HTTP_PROXY & HTTPS_PROXY sur fiddler (http://localhost:8888) et set ignore_ssl à true si le magasin de certificat du réseau est fucké. Pour le moment on sait le faire que en dur dans le source de webdriver-manager ( .\node_modules\gulp-protractor\node_modules\protractor\bin\webdriver-manager ) ligne 93 (default('ignore_ssl', false).)

ATTENTION IE : pour exécution sur IE need drivers spécifiques. (dépendent de la plateforme, sur win x64 : IEDriverServer_x64_2.47.0.zip) Config à faire ensuite dans protractor.conf.js pour lancer sur ce server (géré au niveau du generator)

# lancer la suite de test sur chaque browser

multiCapabilities: [{
    'browserName': 'firefox'
  }, {
    'browserName': 'chrome'
  }, {
    'browserName': 'ie'
  }],

1
2
3
4
5
6
7
8

# tests pour ie fail

UnknownError: The path to the driver executable must be set by the webdriver.ie.driver system property; for more information, see http://code.google.com/p/selenium/wiki/InternetExplorerDriver. The latest version can be downloaded from http://selenium-release.storage.googleapis.com/index.html
1

IE nécessite un driver spécifique et une config spécifique. Dans gulp-protractor les drivers webdriver de IE ne sont pas DL, cf issue 38 Télécharger les drivers IE manuellement ici. Doc du driver ici.

Installer l'exe dans la partie exécutable du disque dur.

Ajouter de la config à protractor.conf.js :

 seleniumArgs: [
    '-Dwebdriver.ie.driver=C:\\Produits\\dev\\ws-js\\webdriver-ie\\IEDriverServer.exe'
  ],
1
2
3

Ajouter la capability à la config de protractor.conf.js :

 multiCapabilities: [{
    'browserName': 'firefox'
  }, {
    'browserName': 'chrome'
  }, {
    'browserName': 'internet explorer',
    'platform': 'ANY',
    'version': '11'
  }],
1
2
3
4
5
6
7
8
9

# exporter les résultats

resultJsonOutputFile: 'e2e.results.json',
1

Nécessite que jasmineNodeOpts.isVerbose soit à true.

# reporter

installation :

npm install jasmine-reporters@^2.0.7 --save-dev
1

Dans protractor.conf.js, ajouter :

var jasmineReporters = require('jasmine-reporters');
exports.config = {
    // ...
    framework: 'jasmine2',
    // ...
    onPrepare: function () {
        jasmine.getEnv().addReporter(new jasmineReporters.JUnitXmlReporter({
        savePath: paths.e2e + '/reports/',
        consolidateAll: true,
        filePrefix: 'e2e.chrome.results.' + _currentTimestampToString()
    }));
    // ...
    // Options to be passed to Jasmine-node.
    jasmineNodeOpts: {
        showColors: true,
        defaultTimeoutInterval: 60000,
        print: function() {}
        }
    },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Va créer le rapport dans ../e2e/reports/

Le rapport est un fichier au format xml qui nécessite une mise en forme. Cette xslt peut être utilisée pour mettre en forme le xml d'output mais manque un moyen pour linker automatiquement le xml de sortie avec la xsl. (pas prévu par le module)