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.

Testing Tools

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:

The next step is to create a new test folder to hold our test files.

mkdir test
 
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)