Angular 7: Testing
After spending hours, days, months on a web app you’re finally ready to release it to the world. Plenty of hard work and time has been poured into it and now it’s time for it to pay off… and then boom: a blocking bug shows up that prevents anyone from signing up.
Test driven?
Testing can help reveal bugs before they appear, instill confidence in your web application, and makes it easy to onboard new developers into the application. There is little doubt about the power of testing amongst the world of software development. However, there is debate about how to go about it.
Is it better to write the tests first and then write the implementation to make those tests pass or would it be better to validate that code that we’ve already written is correct? It’s pretty odd to think that this is a source of contention across the development community, but there is a debate that can get pretty heated as to which is the right way to handle testing.
In our experience, particularly when coming from a prototype-heavy background, we focus on building test-able code. Although your experience may differ, we have found that while we are prototyping applications, testing individual pieces of code that are likely to change can double or triple the amount of work it
takes to keep them up. In contrast, we focus on building our applications in small components, keeping large amounts of functionality broken into several methods which allows us to test the functionality of a part of the larger
picture. This is what we mean when we say testable code.
An alternative methodology to prototyping (and then testing after) is called “Red-Green-Refactor”. The idea is that you write your tests first and they fail (red) because you haven’t written any code yet. Only after you have failing tests do you go on to write your implementation code until it all passes (green).
Of course, the decision of what to test is up to you and your team, however we’ll focus on how to test your applications in this chapter.
End-to-end vs. Unit Testing
There are two major ways to test your applications: end-to-end testing or unit testing.
If you take a top-down approach on testing you write tests that see the application as a “black box” and you interact with the application like a user would and evaluate if the app seems to work from the “outside”. This top-down technique of testing is called End to End testing.
In the Angular world, the tool that is mostly used is called Protractor. Protractor is a tool that opens a browser and interacts with the application, collecting results, to check whether the testing expectations were met.
The second testing approach commonly used is to isolate each part of the application and test it in isolation. This form of testing is called Unit Testing.
In Unit Testing we write tests that provide a given input to a given aspect of that unit and evaluate the output to make sure it matches our expectations.
In this chapter we’re going to be covering how to unit test your Angular apps.
In order to test our apps, we’ll use two tools: Jasmine and Karma.
Jasmine
Jasmine is a behavior-driven development framework for testing JavaScript code.
Using Jasmine, you can set expectations about what your code should do when invoked.
For instance, let’s assume we have a sum
function on a Calculator
object. We want to make sure that adding 1 and 1 results in 2. We could express that test (also called a _spec), by writing the following code:
describe('Calculator', () => { it('sums 1 and 1 to 2', () => { var calc = new Calculator(); expect(calc.sum(1, 1)).toEqual(2); }); });
One of the nice things about Jasmine is how readable the tests are. You can see here that we expect the calc.sub
operation to equal 2.
We organize our tests with describe
blocks and it
blocks.
Normally we use describe
for each logical unit we’re testing and inside that we use one it
for each expectation you want to assert. However, this isn’t a hard and fast rule. You’ll often see an it
block contain several expectations.
On the Calculator
example above we have a very simple object. For that reason, we used one describe block for the whole class and one it
block for each method.
This is not the case most of the times. For example, methods that produce different outcomes depending on the input will probably have more than one it
block associated. On those cases, it’s perfectly fine to have nested describes: one for the object and one for each method, and then different assertions inside individual it
blocks.
We’ll be looking at a lot of describe
and it
blocks throughout this chapter, so don’t worry if it isn’t clear when to use one vs. the other. We’ll be showing lots of examples.
For more information about Jasmine and all its syntax, check out the Jasmine documentation page.
Karma
With Jasmine we can describe our tests and their expectations. Now, in order to actually run the tests we need to have a browser environment.
That’s where Karma comes in. Karma allows us to run JavaScript code within a browser like Chrome or Firefox, or on a headless browser (or a browser that doesn’t expose a user interface) like PhantomJS.
Writing Unit Tests
Our main focus on this section will be to understand how we write unit tests against different parts of our Angular apps.
We’re going to learn to test Services, Components, HTTP requests and more. Along the way we’re also going to see a couple of different techniques to make our code more testable.
Angular Unit testing framework
Angular provides its own set of classes that build upon the Jasmine framework to help writing unit testing for the framework.
The main testing framework can be found on the @angular/core/testing
package. (Although, for testing components we’ll use the @angular/compiler/testing
package and @angular/platform-browser/testing
for some other helpers. But more on that later.)
If this is your first time testing Angular I want to prepare you for something: When you write tests for Angular, there is a bit of setup.
For instance, when we have dependencies to inject, we often manually configure them. When we want to test a component, we have to use testing-helpers to initialize them. And when we want to test routing, there are quite a few dependencies we need to structure.
If it feels like there is a lot of setup, don’t worry: you’ll get the hang of it and find that the setup doesn’t change that much from project to project. Besides, we’ll walk you through each step in this chapter.
As always, you can find all of the sample code for this chapter in the code download. Looking over the code directly in your favorite editor can provide a good overview of the details we cover in this chapter. We’d encourage you to keep the code open as you go through this chapter.
Setting Up Testing
Earlier in the Routing Chapter we created an application for searching for music. In this chapter, let’s write tests for that application.
Karma requires a configuration in order to run. So the first thing we need to do to setup Karma is to create a karma.conf.js
file.
Let’s karma.conf.js
file on the root path of our project, like so:
Since we’re using Angular CLI, this karma.conf.js
file is already created for us! However, if your project does not use Angular CLI, you may need to setup Karma on your own.
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function(config) {
let configuration = {
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../coverage'),
reports: ['html', 'lcovonly'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
};
if (process.env.TRAVIS) {
configuration.customLaunchers = {
Chrome_travis_ci: {
base: 'Chrome',
flags: ['--no-sandbox']
}
};
configuration.browsers = ['Chrome_travis_ci'];
}
config.set(configuration);
};
Don’t worry too much about this file’s contents right now, just keep in mind a few things about it:
- sets PhantomJS as the target testing browser;
- uses Jasmine karma framework for testing;
- uses a WebPack bundle called
test.bundle.js
that basically wraps all our testing and app code;
The next step is to create a new test
folder to hold our test files.
This page is a preview of ng-book 2.
Get the rest of this chapter plus hundreds of pages Angular 7 instruction, 5 sample projects, a screencast, and more.
Ready to master Angular 7?
- What if you could master the entire framework – with solid foundations – in less time without beating your head against a wall? Imagine how quickly you could work if you knew the best practices and the best tools?
- Stop wasting your time searching and have everything you need to be productive in one, well-organized place, with complete examples to get your project up without needing to resort to endless hours of research.
- You will learn what you need to know to work professionally with ng-book: The Complete Book on Angular 7 or get your money back.
Download the First Chapter (for free)