We write unit tests to explore and confirm the behavior of parts of our application.
We like having unit tests for many reasons, three of them in particular:
- They guard against breaking existing code (“regressions”) when we make changes.
- They clarify what the code does both when used as intended and when faced with deviant conditions.
- They reveal mistakes in design and implementation. Tests force us to look at our code from many angles. When a part of our application seems hard to test, we may have discovered a design flaw, something we can cure now rather than later when it becomes expensive to fix.
While we like having tests, we don’t always like making tests. It’s a bit like the difference between having money (yeah!) and making money (oof!).
Our lofty goal in this chapter is make it easy for you to write Angular application tests. If that goal exceeds our reach, we can at least make testing easier… and easy enough that you’ll want to write tests for your application.
The Testing Spectrum
Exploring behavior with tests is called “Functional Testing”. There are other important forms of testing too such as acceptance, security, performance, and deployment testing. We concentrate on functional testing in this section on unit testing.
There is considerable variety within functional testing, a spectrum from short, quick pure unit tests to long running end-to-end tests. We could classify them in broad groups
Pure unit test | We test the part in isolation. Either the part has no dependencies or we fake all of its dependencies during the test. |
Close-in integration | We test a part as it collaborates with closely related parts and/or with the Angular framework. We may fake some of its dependencies. |
High level integration | We test a part as it interacts with many, wide-ranging aspects of the system including the browser DOM. Such tests are often asynchronous which means the test runner must pause until the entire tested sequence completes. |
Cross network integration | A more demanding “high level integration” test that reaches across the network boundary to touch a test server such as tests of a data service that exercise both client and server components. |
End-to-End (E2E) | We simulate the actions of users as they navigate through the application. Such tests strive to investigate the behavior of the application as a whole, replicating the user experience as faithfully as possible. |
Each kind of test has its strengths and weaknesses. The tests to the left are typically easier to write, more focused, more robust, and they often run faster than the tests to the right. It’s much easier to create adverse, stressful conditions for a pure unit test than an end-to-end test. We get a lot of value for comparatively little effort. That may be why we will write a lot more unit tests than end-to-end tests.
On the other hand, the shorter tests can’t reveal the integration problems that may be hiding at the boundaries within the actual application. We can simulate problems that we imagine might arise. But we will miss the problems in the real system that we didn’t anticipate.
We need the full spectrum of tests to assure the behavioral quality of the entire application.
In this Unit Testing section we learn to write tests in all of these categories except end-to-end. We cover end-to-end tests in a separate section because they require special tools and considerations.
We’ll begin on the left side of the spectrum, with pure unit tests of simple parts such as the Hero
model class and a custom pipe named InitCapsPipe
. Then we work our way to the right as we explore the more complex parts of our application such as components, the router, and remote data access.
We’ll learn a number of tips and tricks along the way, including:
- what’s worth testing and what isn’t
- when a test is telling us to rethink our design
- how to debug our tests
- how to write asynchronous tests
- when to mock and how
Unit Testing Chapters
Here is what we’ll learn in the unit testing chapters.
- Jasmine Testing 101
- setup to run Jasmine tests in the browser
- basic Jasmine testing skills
- write simple Jasmine tests in TypeScript
- debug a test in the browser
- The Application Under Test
- Test a class
- test a simple application class outside of Angular
- where to put the test file
- load a test file with systemJS
- Test a Pipe
- test a simple Angular Pipe class
- add the Angular 2 library to the test harness
- load multiple test files using system.js
- Test an Asynchronous Service
- test an asynchronous service class outside of Angular
- write a test plan in code
- fake a dependency
- master the
catch(fail).then(done)
pattern - move setup to
beforeEach
- test when a dependency fails
- control async test timeout
- The Angular Test Environment
- the Angular test environment and why we need help
- add the Angular Test libraries to the test harness
- test the same async service using Angular Dependency Injection
- reduce friction with test helpers
- introducing spies
- Test a Component
- test the component outside of Angular
- mock the dependent asynchronous service
- simulate interaction with the view (no DOM)
- use a spy-promise to control asynchronous test flow
- Test a Component in the DOM
- test the component inside the Angular test environment
- use the
TestComponentBuilder
- more test helpers
- interact with the DOM
- bind to a mock dependent asynchronous service
- Run the tests with karma
It’s a big agenda. Fortunately, you can learn a little bit at a time and put each lesson to use.
The Unit Testing chapters build upon each other. We recommend reading them in order.
We're also assuming that you're already comfortable with basic Angular 2 concepts and the tools
we introduced in the QuickStart and
the Tour of Heroes tutorial
such as npm
, gulp
, and live-server
.