Most Angular 2+ testing tutorials (including the official tutorial on angular.io) recommend
the wrong way to test Components.
Consider the following component:
and its test code:
The test code reaches into the component to modify
the value of title.
This is a common practice you’ll find in most tutorials,
and it is problematic in a couple of ways.
Problem 1: The test is fragile, and can break even when the implementation is correct.
This test makes assumptions into the implementation of SutComponent,
i.e. it assumes that the component has a field called title.
However, SutComponent was intended to have a property title
that a parent could bind to,
and not a public field named title.
If for some reason we need the field to be someOtherName,
but the property name remains as title, as illustrated below:
This implementation would still work correctly in the application
but the test would now fail.
Problem 2: The test is incomplete, it can pass even if implementation is broken.
For the same reason, the original test does not exercise the property binding
title.
If we remove the property binding but keep the field title,
as illustrated in the code below,
the component would now not work as intended,
but the test above would still pass and not catch the error.
The Right Way
To test Angular 2+ Components correctly,
our tests must be written without knowledge or assumption
of the component’s implementation details.
Tests must be written to test a component’s public interfaces and contracts and only
its public interfaces and contracts.
What are the public interfaces of Angular 2+ Components?
A well-encapsulated component can have the following types of public interfaces:
Property binding, as illustrated above
Event bindings, through which a component can emit an event to its parent
UI rendering and interactions
Interactions with models, business logic, and external systems through
dependency-injected services
These must be the only interfaces used in the Angular 2+ component test code.
Consider the following component ArticleComponent that contains
all these types of public interfaces:
You can see that we declare all the fields and methods as private.This would not affect the application, and it helps enforcing the strict encapsulation during development. (Update 2017-04-19: Private fields are no longer accepted for production build. However, to preserve modularity, fields should not be accessed directly from outside its component.)
To test ArticleComponent, we start by creating a mock parent component that
binds to the properties and events of ArticleComponent.
We also create a mock instance of ArticlesService,
mockArticlesService, with the interfaces we intend to use in the component:
Instead of instantiating ArticleComponent,
we instantiate the TestParent component which
binds to the properties and events of ArticleComponent.
The ArticleComponent test fixture is then extracted from the TestParent fixture.
To test the interactions between ArticleComponent and ArticlesService,
mockArticlesService is injected as a provider for ArticlesService.
We then spy on this mock service instance using jasmine’s spyOn.
Note that spyOn must be called on the ArticlesService instance
that provided by TestBed,
not on the mockArticlesService object we created.
The tests are then performed on these instances:
Putting it all together, the test code reads:
These tests exercise the intended interfaces and contracts of the component,
in the ways that the component would be used in the application.
Summing Up
In this article, you should have learned:
Why most Angular 2+ Component testing tutorial are wrong
What are the public interfaces of an Angular 2+ Component and how to test them