One of the reasons AngularJS was introduced was a problem of testing front-end apps. It solved that problem quite nicely and version 2 follows that path despite being a totally new piece of code. What makes it a good choice is a separation of concerns. We’ll focus mostly on unit testing here. Its responsibility is to ensure that every single function of a system works fine without being even aware of any other part. If the chosen framework (or library) doesn’t help with such a separation, and doesn’t give ability of mocking dependencies, it is really hard to do it right over time (or even impossible). We won’t focus on the test setup, so please take your time to use angular-cli or any other starter project that suites you to follow. If you’re not familiar with testing that’s OK. We’ll start right now from the very beginning. Later we’ll move to tests connected directly to our app.
The place to start with is a component. The very basic ‘Hello World’ example looks like this:
import
{
Component
}
from
'@angular/core'
;
@
Component
({
selector
:
'app'
,
template
:
'<span>Hello</span>'
})
export
class
App
{}
It’s basically a class with a decorator, which adds some logic Angular makes use of. The important thing is decorators add some stuff to the class. It means we can still do: new App()
and end up with a pure JavaScript object!
Now let’s add some property to the class:
@
Component
({
selector
:
'app'
,
template
:
'<span>{{ hello }}</span>'
})
export
class
App
{
public
hello
:string
=
'Hello'
;
}
And now let’s move to tests. First, you must place the tests in a directory. One way is to keep unit tests as close to the code as possible, and the other is to keep all tests in separate directory. There’s no better solution in fact. Each one has good and bad parts. The choice is yours, but remember, stick to the rules you’ve created. It’s actually more important than the choice.
If we have a file like app.component.ts
, which contains the code from the snippet above, then we would create a file named app.component.spec.ts
or app.component.test.ts
. Put the following in there:
import
{
App
}
from
'./app.component'
;
describe
(
'App'
,
()
=>
{
it
(
'should have hello property'
,
()
=>
{
this
.
app
=
new
App
();
expect
(
this
.
app
.
hello
).
not
.
toBe
(
'Hello'
);
});
});
It’s good to write failing tests first and then make it passing – even when no TDD is introduced. It’s just about making sure that a particular test is running and it is not a false positive. In Jasmine you can see a statement like expect(...).not.toBe(...)
.
We are now able to test Services, Pipes, basic Components and everything what is a JavaScript class.
Let’s move forward and add a method to the component:
@
Component
({
selector
:
'app'
,
template
:
'<span>{{ sayHello() }}</span>'
})
export
class
App
{
public
name
:string
=
'John'
;
sayHello
()
:
string
{
return
`
Hello
$
{
this
.
name
}
`
;
}
}
And the actual test looks like this:
describe
(
'App'
,
()
=>
{
beforeEach
(()
=>
{
this
.
app
=
new
App
();
});
it
(
'should have name property'
,
()
=>
{
expect
(
this
.
app
.
name
).
toBe
(
'John'
);
});
it
(
'should say hello with name property'
,
()
=>
{
expect
(
this
.
app
.
sayHello
()).
toBe
(
'Hello John'
);
});
});
As far as we know services are nearly pure classes. What makes them different is the ability to inject and be injected. But still it makes them the easiest one to start with. Let’s start with the very beginning:
class
TestService
{
public
name
:string
=
'Injected Service'
;
}
We can go ahead and use something you’ve already learned:
describe
(
'TestService'
,
()
=>
{
beforeEach
(()
=>
{
this
.
testService
=
new
TestService
();
});
it
(
'should have name property set'
,
()
=>
{
expect
(
this
.
testService
.
name
).
toBe
(
'Injected Service'
);
});
});
Pure service tests can look just like that. But consider following function: bootstrap(App, [TestService])
. What it really does is to provide the service to be ready to be injected through the whole App. It means every time we need that service we can simply use the DI. Things get a little bit more complicated when we want to test such a behavior. First of all we won’t use pure Jasmine functions any more. This is what is about to happen:
import
{
async
,
it
,
describe
,
expect
,
inject
,
beforeEach
,
beforeEachProviders
}
from
'@angular/core/testing'
;
Note that all of the helpers from Jasmine were replaced by their equivalents from @angular/core/testing
. This is because Angular 2 relies heavily on Dependency Injection, and this is not something that Jasmine is aware of. The Angular team made wrappers that add their own logic. The outcome is we can now use inject()
instead of an anonymous callback function in it
(or beforeEach
) that will inject some class.
The test that conforms the bootstrapping with a given provider would look like the following:
describe
(
'TestService Injected'
,
()
=>
{
beforeEachProviders
(()
=>
[
TestService
]);
it
(
'should have name property set'
,
inject
([
TestService
],
(
testService
:TestService
)
=>
{
expect
(
testService
.
name
).
toBe
(
'Injected Service'
);
}));
});
This way we should end up with a tested injected service.
One more thing will be required now. We have to set providers for tests to be able to run in the real browser:
import
{
setBaseTestProviders
}
from
'@angular/core/testing'
;
import
{
TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS
,
TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS
,
}
from
'@angular/platform-browser-dynamic/testing'
;
setBaseTestProviders
(
TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS
,
TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS
);
So far we haven’t solved the problem of Dependency Injection. The last piece is mocking. The whole idea behind the Angular testability is to replace the real provider with a mocked one to ensure that tests are isolated. Mocking it’s really straightforward:
import
{
provide
}
from
'@angular/core'
;
class
MockTestService
{
public
mockName
:string
=
'Mocked Service'
;
}
describe
(
'TestService Mocked'
,
()
=>
{
beforeEachProviders
(()
=>
[
provide
(
TestService
,
{
useClass
:MockTestService
})
]);
it
(
'should have name property set'
,
inject
([
TestService
],
(
testService
:TestService
)
=>
{
expect
(
testService
.
mockName
).
toBe
(
'Mocked Service'
);
}));
});
In the test case code doesn’t know what exactly the TestService
is. It simply doesn’t care – it’s completely transparent. This way we can test the behavior of the component itself, not its dependencies. Moreover, we can use something from both worlds – the real one and the mocked (if really needed) using simple JavaScript inheritance:
class
MockTestServiceInherited
extends
TestService
{
public
sayHello
()
:
string
{
return
this
.
name
;
}
}
describe
(
'TestService Mocked Inherited'
,
()
=>
{
beforeEachProviders
(()
=>
[
provide
(
TestService
,
{
useClass
:MockTestServiceInherited
})
]);
it
(
'should say hello with name'
,
inject
([
TestService
],
(
testService
:TestService
)
=>
{
expect
(
testService
.
sayHello
()).
toBe
(
'Injected Service'
);
}));
});
Injecting services themselves to test suites is not what you really need. They can be tested using pure classes (unless you want to inject service into service). The point is they gives the possibility to fully test a component. But let’s leave it for now and grab some component.
Let’s say that our component is a list of items (e.g. tasks). It’s a dumb component – all it does is rendering a list for given input. It’s as simple as this:
import
{
Component
,
Input
}
from
'@angular/core'
;
import
{
NgFor
}
from
'@angular/common'
;
@
Component
({
selector
:
'list'
,
template
:
'<span *ngFor="let task of tasks">{{ task }}</span>'
,
directives
:
[
NgFor
]
})
class
ListComponent
{
public
tasks
:string
[]
=
[];
}
Angular 2 forces us to do a little bit now to create such a component in test:
describe
(
'ListComponent'
,
()
=>
{
it
(
'should render list'
,
inject
([
TestComponentBuilder
],
(
tcb
:TestComponentBuilder
)
=>
{
return
tcb
.
createAsync
(
ListComponent
).
then
(
componentFixture
=>
{
const
element
=
componentFixture
.
nativeElement
;
componentFixture
.
componentInstance
.
tasks
=
[
'Learn ng2'
];
componentFixture
.
detectChanges
();
expect
(
element
.
querySelectorAll
(
'span'
).
length
).
toBe
(
1
);
});
}));
});
We don’t want to go in depth when it comes to the provided API, but you should really know these methods of ComponentFixture
:
nativeElement
- allows to access DOMcomponentInstance
- allows to access properties of a componentdetectChanges
- force Angular to run change detectionMoreover inject
allows us to have Promise
as a test result. This Promise
is being created when called createAsync
method on TestComponentBuilder
. The rest is pretty straightforward. We set array to be some mocked value, say to Angular to detect changes and then check if everything rendered successfully.
As part of successful rendering we should also test events attached inside a particular component. Now consider a component, which is just listening to the click
event:
import
{
Component
,
Input
}
from
'@angular/core'
;
@
Component
({
selector
:
'click-handler'
,
template
:
'<button (click)="sendEvent()"></button>'
,
})
class
ClickHandlerComponent
{
public
sendEvent
()
:
void
{
// do nothing
}
}
To test it we need to introduce the spyOn
function that tracks the function invocations. You should do the following:
describe
(
'ClickHandlerComponent'
,
()
=>
{
it
(
'should invoke a method when clicked'
,
inject
([
TestComponentBuilder
],
(
tcb
:TestComponentBuilder
)
=>
{
return
tcb
.
createAsync
(
ClickHandlerComponent
).
then
(
componentFixture
=>
{
spyOn
(
componentFixture
.
componentInstance
,
'sendEvent'
);
const
element
=
componentFixture
.
nativeElement
;
const
clickableElement
=
element
.
querySelector
(
'button'
);
clickableElement
.
click
();
componentFixture
.
detectChanges
();
expect
(
componentFixture
.
componentInstance
.
sendEvent
).
toHaveBeenCalled
();
});
}));
});
Now we are sure that the event handler was attached successfully and the method has been called properly. Further, we can use more advanced Jasmine matchers to check whether methods were invoked with proper arguments and so on.
Many of the components in your app will be just a dumb components. It means they only have to know about the output and the input. Testing @Input()
is nothing more just setting proper value. With @Output
we do have a similar thing. In unit testing we don’t really want to test internal Angular 2 functions, we just want to make sure our code is working properly. We’ll cover it according to the previous example. Consider the following component:
import
{
Component
,
EventEmitter
,
Input
,
Output
}
from
'@angular/core'
;
@
Component
({
selector
:
'click-handler'
,
template
:
'<button (click)="sendEvent()"></button>'
,
})
class
ClickHandlerComponent
{
@
Output
()
public
onButtonClicked
:EventEmitter
<
boolean
>
=
new
EventEmitter
();
public
sendEvent
()
:
void
{
this
.
onButtonClicked
.
emit
(
true
);
}
}
The point is to test whether onButtonClicked
is emitting a value each time sendEvent
is invoked. To achieve this we can inject component and subscribe to the event emitter:
describe
(
'ClickHandlerComponent'
,
()
=>
{
beforeEachProviders
(()
=>
[
ClickHandlerComponent
]);
beforeEach
(
inject
([
ClickHandlerComponent
],
(
clickHandlerComponent
:ClickHandlerComponent
)
=>
{
this
.
component
=
clickHandlerComponent
;
}));
it
(
'should emit event when a sendEvent method is called'
,
done
=>
{
this
.
component
.
onButtonClicked
.
subscribe
((
event
:boolean
)
=>
{
expect
(
event
).
toBe
(
true
);
done
();
});
this
.
component
.
sendEvent
();
});
});
Now we’re using the jasmine async version of the it
callback to achieve the desired result. Usage of EventEmitter
implies such a syntax as it’s no more a Promise
to be resolved. So the first thing inside the actual test is to subscribe to the emitter and then invoke a method that should use that emitter. Note that we’re not using @Output()
anywhere in the test. We’re just checking the emitter behavior.
Every entity in Angular 2 Pipe is also a class with proper annotation. The nice thing about Pipes is they are very small, easy to understand, and easy to test. Let’s say we have to truncate some text and implement a pipe for that:
import
{
Pipe
,
PipeTransform
}
from
'@angular/core'
;
@
Pipe
({
name
:
'truncate'
})
class
TrancatePipe
implements
PipeTransform
{
public
transform
(
value
:string
)
:
string
{
return
value
.
length
>
10
?
`
$
{
value
.
substring
(
0
,
10
)}...
`
:
value
;
}
}
All the purpose of the above pipe is to cut everything after the 10th letter and add there an ellipsis. To make a proper test we can use everything we’ve learned so far. Pipe is just a specific class. So all we need is to test the transform
method:
describe
(
'TrancatePipe'
,
()
=>
{
beforeEachProviders
(()
=>
[
TrancatePipe
]);
beforeEach
(
inject
([
TrancatePipe
],
(
trancatePipe
:TrancatePipe
)
=>
{
this
.
pipe
=
trancatePipe
;
}));
it
(
'should truncate long text'
,
()
=>
{
expect
(
this
.
pipe
.
transform
(
'very long text'
)).
toBe
(
'very long ...'
);
});
it
(
'should NOT truncate short text'
,
()
=>
{
expect
(
this
.
pipe
.
transform
(
'short one'
)).
toBe
(
'short one'
);
});
});
The fact that a given pipe is stateless and allowed as to move injection of the pipe to the beforeEach
, but be very careful with that. Ideally, unit tests should be as separated as possible, but it sometimes conflicts with DRY (Don’t Repeat Yourself) so we strongly advise you have balance there. Sometimes it’s worth it to copy some init in a test due to the readability and reasoning about the code.
Everything above is fine up to the point when you meet Router
. This one requires to add some additional steps to make tests work. Let’s start with the following snippet:
import
{
Component
}
from
'@angular/core'
;
import
{
ROUTER_DIRECTIVES
,
Routes
}
from
'@angular/router'
;
import
{
ChildComponent
}
from
'./components/child.component'
;
@
Component
({
selector
:
'app'
,
template
:
``
,
directives
:ROUTER_DIRECTIVES
})
@
Routes
([
{
path
:
'/'
,
component
:ChildComponent
}
])
class
AppComponent
{}
Note, we just added only the directives (ROUTER_DIRECTIVES
) with no template. Test for that component would start with this:
describe
(
'AppComponent'
,
()
=>
{
it
(
'should be able to test'
,
inject
([
TestComponentBuilder
],
(
tcb
:TestComponentBuilder
)
=>
{
return
tcb
.
createAsync
(
AppComponent
).
then
(
componentFixture
=>
{
componentFixture
.
detectChanges
();
expect
(
true
).
toBe
(
true
);
});
}));
});
It’s still fine. But the problem is that @Routes
just sets properties and doesn’t make our component work as expected. We have to add a place where the component for the given path will be rendered. There’s a directive for that called RouterOutlet
. Here it goes:
import
{
Component
}
from
'@angular/core'
;
import
{
ROUTER_DIRECTIVES
,
Routes
}
from
'@angular/router'
;
import
{
ChildComponent
}
from
'./components/child.component'
;
@
Component
({
selector
:
'app'
,
template
:
`
<
router
-
outlet
><
/router-outlet>`,
directives
:ROUTER_DIRECTIVES
})
@
Routes
([
{
path
:
'/'
,
component
:ChildComponent
}
])
class
AppComponent
{}
Now our test fails with:
ORIGINAL EXCEPTION: No provider for Router!
To fix that we have to provide a fake router. To achieve this we are able to import ROUTER_FAKE_PROVIDERS
and simply add it into the beforeEach
section:
describe
(
'AppComponent'
,
()
=>
{
beforeEachProviders
(()
=>
[
ROUTER_FAKE_PROVIDERS
])
it
(
'should be able to test'
,
inject
([
TestComponentBuilder
],
(
tcb
:TestComponentBuilder
)
=>
{
return
tcb
.
createAsync
(
AppComponent
).
then
(
componentFixture
=>
{
componentFixture
.
detectChanges
();
expect
(
true
).
toBe
(
true
);
});
}));
});
As you can see, it is pretty straightforward to satisfy the app about the router. The problem really begins when you want to test the routing itself. It very quickly ends up with imports like this:
import
{
ComponentResolver
}
from
'@angular/core'
;
import
{
Location
}
from
'@angular/common'
;
import
{
DefaultRouterUrlSerializer
,
RouteSegment
,
Router
,
RouterOutletMap
,
RouterUrlSerializer
}
from
'@angular/router'
;
import
{
beforeEach
,
beforeEachProviders
,
describe
,
expect
,
fakeAsync
,
inject
,
it
,
tick
}
from
'@angular/core/testing'
;
import
{
SpyLocation
}
from
'@angular/common/testing'
;
import
{
TestComponentBuilder
}
from
'@angular/compiler/testing'
;
What happened here? Now we do have to make a lot of mocking that we are used to. By a lot, we mean:
beforeEachProviders
(()
=>
[
RouterOutletMap
,
{
provide
:Location
,
useClass
:SpyLocation
},
{
provide
:RouterUrlSerializer
,
useClass
:DefaultRouterUrlSerializer
},
{
provide
:Router
,
deps
:
[
ComponentResolver
,
RouterUrlSerializer
,
RouterOutletMap
,
Location
],
useFactory
:
(
resolver
,
urlParser
,
outletMap
,
location
)
=>
new
Router
(
'AppComponent'
,
AppComponent
,
resolver
,
urlParser
,
outletMap
,
location
)
}
]);
We are now about to test the following routing applied to the component:
@
Routes
([
{
path
:
'/test'
,
component
:TestComponent
}
])
The test itself is pretty nice-looking:
it
(
'should be able to navigate to TestComponent'
,
fakeAsync
(
inject
([
Router
,
Location
,
TestComponentBuilder
],
(
router
:Router
,
location
:Location
,
tcb
:TestComponentBuilder
)
=>
{
tcb
.
createFakeAsync
(
AppComponent
);
router
.
navigate
([
'/test'
]);
tick
();
expect
(
location
.
path
()).
toBe
(
'/test'
);
})
));
This time we used fakeAsync
. It let’s us create asynchronous tests in a synchronous way. The key here is a tick
function. It says to the Angular to finish the jobs that just left. In this case it’s a navigation.
The common pattern is to keep connection with back-end outside the component and simply not to inject http
directly into the component. The right place seems to be a service. So let’s create one:
import
{
Injectable
}
from
'@angular/core'
;
import
{
Http
}
from
'@angular/http'
;
@
Injectable
()
export
class
TestService
{
constructor
(
private
http
:Http
)
{}
getUsers() {
return
this
.
http
.
get
(
'http://foo.bar'
);
}
}
You know pretty much everything that happened right now, so let’s move to the test. These imports will be useful:
import
{
BaseRequestOptions
,
Response
,
ResponseOptions
}
from
'@angular/http'
;
import
{
MockBackend
,
MockConnection
}
from
'@angular/http/testing'
;
Now, let’s use it and prepare proper services to be injected:
beforeEachProviders
(()
=>
[
TestService
,
BaseRequestOptions
,
MockBackend
,
provide
(
Http
,
{
useFactory
:
(
backend
:MockBackend
,
defaultOptions
:BaseRequestOptions
)
=>
{
return
new
Http
(
backend
,
defaultOptions
);
},
deps
:
[
MockBackend
,
BaseRequestOptions
]
})
]);
One thing to notice is the Http
mock. Each time when something needs the Http
we are using our concrete implementation. Moreover, we are injecting dependencies into the factory. It’s worth it to add that in case factory returns an instance of class it won’t be a singleton.
With mocked Http
there’s one thing left before the actual test. I’ve mocked all HTTP calls to return a simple string:
beforeEach
(
inject
([
MockBackend
],
(
backend
:MockBackend
)
=>
{
const
baseResponse
=
new
Response
(
new
ResponseOptions
({
body
:
'got response'
}));
backend
.
connections
.
subscribe
((
c
:MockConnection
)
=>
c
.
mockRespond
(
baseResponse
));
}));
And finally the test itself:
it
(
'should return response when subscribed to getUsers'
,
inject
([
TestService
],
(
testService
:TestService
)
=>
{
testService
.
getUsers
().
subscribe
((
res
:Response
)
=>
{
expect
(
res
.
text
()).
toBe
(
'got response'
);
});
})
);
We just tested that every time we call getUsers
it makes an HTTP call.