Published

AngularJS tests with mocked HTML documents

Most of the time, testing AngularJS applications means testing JavaScript. We test services and we test controllers. But what about directives? What about components? How do we test compiled and injected AngularJS code?

Plunker

Testing directives/components

Around a year ago I wrote a post on testing AngularJS directives with Jasmine. Let’s take a look at this article with a slightly modified example. While I used a directive in my last post back in 2016, we use one of AngularJS’ components, now. It’s 2017!

The component shows a simple text field which is required. The textForm becomes valid if some text is filled into this required text field.

The test

Testing this simple component is very straight forward. We just need to compile the HTML and run our tests against it. As AngularJS components use an isolated scope, we need to call element.isolateScope() to get the scope (including our form) of the component.

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

describe('Text directive',function(){

var$compile;

var$rootScope;

beforeEach(function(){

module('plunker');

inject(function(_$compile_,_$rootScope_){

$compile=_$compile_;

$rootScope=_$rootScope_;

});

});

it('should be invalid as text field is empty but required',function(){

varelement=$compile('<text></text>')($rootScope);

$rootScope.$digest();

expect(element.isolateScope().textForm.$valid).toBe(false);

});

it('should become valid if text is entered in required text field',function(){

varelement=$compile('<text></text>')($rootScope);

$rootScope.$digest();

$rootScope.$$childTail.textForm.name.$setViewValue("some text");

expect(element.isolateScope().textForm.$valid).toBe(true);

});

});

So what about the document?

Although we compiled some HTML code in the example above, we didn’t use any test document! There was no need. So let’s extend our example a little bit and introduce some necessity to use a test document.

Image the following: We have some use case where we need to generate code. If an user has not entered his phone number yet, we display an input form. If the user has already entered his phone number, we display some text (“Thank you for entering your phone number!“). While we could easily use some ng-if or ng-show construction, we decided to do it with code generation.

This use case could look like this:

1

2

3

4

5

6

7

8

9

10

app.component('generator',{

template:"<div id=\"component-container\"></div>",

controller:function($compile,$scope,$document){

varcomponentContainer=$document.find('#component-container');

vartemplate="<text></text>";

varchildScope=$scope.$new();

varresult=componentContainer.append(template);

$compile(result)(childScope);

}

});

We define a component called generator. This component has a very simple template, containing a placeholder div where we want to inject some other code. The main point is, that we are using the $document service. This service operates on the current document and is our way to create a mock document. But first, let’s see what happens if we don’t use a mock document.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

describe('Generator directive',function(){

var$compile;

var$rootScope;

beforeEach(function(){

module('plunker');

inject(function(_$compile_,_$rootScope_){

$compile=_$compile_;

$rootScope=_$rootScope_;

});

});

it('should be invalid as generated text form is empty but required',function(){

varelement=$compile('<generator></generator>')($rootScope);

$rootScope.$digest();

// THIS WILL NOT WORK! Although we compiled the 'generator' directive just like the 'text'