Angular Corner: Pro Unit Testing

Welcome to Angular Corner! This is the second in a series of articles that provide strategic advice and practical tips for developing high-quality web applications using Angular. If you haven’t done so already, check out our first post on getting started with Angular. This post specifically covers unit testing in Angular. Note: this article assumes you’re familiar with Angular programming, though some concepts and advice apply to newbies as well.
Unit testing in Angular is a huge topic worthy of its own book, so we’ll focus on a few major ideas and provide realistic examples, rather than create a full tutorial. If you’re looking for more detail, check out this github repo for that contains the example unit tests mentioned in a full Angular CLI project. And for a full tutorial, I refer you to the Testing section of the Angular website.

Why Build Unit Tests at all?

Before we get into the technical details, we should ask: are unit tests even worth the effort? The answer, of course, is: it depends. This heated ideological debate rivals that of “best programming language” or “best text editor or IDE.” Successful software projects have been completed both with and without unit tests. But as with anything else, one would hope an informed rational decision would prevail.
What’s not debatable among professional technologists is that developers must test code as they write it (as well as have one or more independent layers of QA testing). When a developer says, “I’m done with the code, but haven’t tested it,” it’s a sure bet that for all but the most trivial updates, that code won’t work. The cheapest way to find and fix defects, of course, is for the developer to find them in the code he just wrote. Otherwise, you wind up stuck in a back-and-forth process to get defects debugged.
Unit testing is a practice by which developers write tests in code so they can be rerun over and over. In exchange for the upfront investment of extra time to develop the unit tests (and perhaps to learn how to perform unit testing in general), you gain a suite of tests that can be run on-demand which provides instant feedback on whether changes have broken existing functionality. This leads to better quality in general, and fewer defects to be found by QA and in production.

Are Unit Tests Worth the Effort?

But the big question is whether or not it’s worth the effort. To help answer this question, consider where your software lies on the following spectrums.

The closer you are to red, the more testing you’re going to need and the greater the likelihood that unit testing should play a role. The closer you are to violet, the more likely unit testing isn’t worth it. For me, simply being in the yellow is enough to warrant unit testing. But I probably wouldn’t bother with unit tests if I were in the blue for all questions.
There are of course other considerations. Firstly, some people have already made up their mind that unit tests are bad or a waste of time. This may be based partly on myth (or possibly on their own experience). Forcing these developers to write unit tests requires a culture change that takes time and effort. Also, introducing unit tests may be a disruption that jeopardizes tight deadlines. Some have never performed unit testing and would need time to become proficient in it. All of these considerations should factor into the decision to do unit tests or not; all projects involve a trade-off between long-and-short-term goals. One thing’s for sure, however. In the long run, you will be well served by having a consistent, well-designed set of unit tests.
As you consider these additional factors around unit testing, it’s worth noting that the 2018 VersionOne State of Agile Report found that 75% of respondents use unit tests. The practice is certainly enjoying widespread popularity among agile developers today.

Unit Testing in Angular

Angular brings some unique benefits to unit testing—as well as some challenges. First off, the structure provided by Angular (and the tooling provided by Angular CLI) makes unit testing immediately possible. The units to be tested are your Angular components, services, directives, and pipes. Dependency injection provides a vehicle to mock-up dependencies and achieve isolation in your unit tests. Right from the start, excellent tools are integrated for building test suites and mocking (Jasmine) and running in-browser tests (Karma). By contrast, in more traditional web app development (using jQuery, for example), it’s not immediately obvious what the units to be tested even are. Some thought and setup is required before you even begin. Thankfully, one advantage of Angular is being able to unit-test right from the start.
Getting started with Angular unit testing requires no setup when you’re using Angular CLI. Everything is already set from the start. Each time you create a component, service, pipe, or directive with ng generate a spec file is automatically created to contain the unit tests for it. Run ng test from the command line and browser automatically opens, running your unit tests (and automatically rerunning them as files are updated). Use the jasmine library to define your test suites via the describe function. Use the beforeEach function to encapsulate test suite setup. Use the it functions to define your tests. And use the expect function to make your test assertions. The general test suite outline for an ordinary class looks like this:
describe(‘MyClass’, () => {
let somethingToTest;
let someDependency;
beforeEach(() => {
 someDependency = jasmine.createSpyObj(‘SomeDependency’, [‘dependentMethod’]);
somethingToTest = new MyClass(someDependency);
});
it(‘should call dependentMethod when start is called’, () => {
somethingToTest.start();
expect(someDepdendency.dependentMethod).toHaveBeenCalled();
});
});

Component Unit Testing

In Angular, unit testing a service or pipe greatly resembles unit testing a class in any object-oriented language. But an Angular component is different because it is a class combined with an HTML template. The HTML template usually contains some executable code in the form of ngIf and ngFor directives, one-way and two-way databinding and typescript expression in various forms and is, therefore, fairly code-like in many respects. Angular component unit tests offer the ability to unit-test the component as a whole—meaning its Typescript class in conjunction with its HTML template. This is great, because otherwise we’d be left testing just the Typescript and ignoring all the view-functionality in the templates.
Setting up an Angular component for unit testing is done through the TestBed class. The configureTestingModule method sets up a miniature Angular application with a set of components available to Angular and a set of dependencies Angular can inject. You can even import other modules, if necessary, such as the Angular FormsModule. The idea is to create your component with all its dependencies (usually mocks or stubs) in the DOM so the test code may interact with it as a user would.
The TestBed.createComponent method creates a ComponentFixture object which accomplishes many things. It provides a reference to the component being tested (fixture.componentInstance) so you can inspect it and call methods on it. Convenient methods exist for inspecting the DOM and triggering events (such as mouse clicks), so we don’t need to use the clunky low-level Javascript APIs. We can invoke Angular change detection as needed to see the DOM updated in response to model changes.
In the interest of space, we’ll hold off on further unit test examples until we’ve discussed some additional major ideas.

Handling Child Components

When the component being unit tested uses other components (which is usually the case) you have a choice to make. Either have Angular inject the actual child component or inject a stub for it. Let’s discuss the pros and cons of each option.
Using the actual component is sometimes very easy. Just drop it in and you’re done. But other times, you drop the component in and then realize it uses some other components, so you have to drop those in as well, and so on recursively until you’ve satisfied all the dependencies. This can be a huge pain and flies in the face of testing a unit in isolation. For some low-level components this may be acceptable. But for high-level components, “unit” tests quickly turn into full app integration tests.
Furthermore, Angular currently has to compile the templates for all components of unit test for every test. So, the more baggage that is pulled-in, the longer the unit tests will take to run. Once you’ve amassed a set of hundreds of unit tests and check results every time you save a change, this is something you’ll start to care about.
Creating a stub component is usually pretty easy. Just make a version of the component with the same CSS selector as the original and an empty html template. For example, if you’re testing a component that makes use of an SpinnerComponent, you’d create a stub like:

@Component({selector: 'app-spinner', template: '' })
export class SpinnerStubComponent { }

If the original component has inputs and outputs your component references, you need to declare those in the stub as well.
I generally stub-out child components like this and only use the original in special circumstances when a stub is not so easy to create and the performance cost of compiling the extra dependencies isn’t terribly high.

Dealing with Asynchronicity

Often Angular unit tests can be ordinary synchronous code where calls are made and results are evaluated. But Javascript (and therefore Typescript) is asynchronous in nature, and every blocking operation is an asynchronous call. This is why Angular is built around the Observable from rxjs. Before long, you’ll need to unit-test a component that makes asynchronous calls to load and save data from the network and you’ll have to deal with asynchronicity in your unit tests. Fortunately, Angular fully supports asynchronous unit testing.
Before we start, we need a few helper functions to create Observables. We could use the of function from rxjs, but that creates synchronous Observables. In many cases, this is fine. We can avoid asynchronicity by replacing it with synchronicity. The problem arises when we need our component to go through a certain state while waiting for asynchronous results. For example, to test that an animated spinner is displayed while data is loading, you need an actual asynchronous Observable.
What’s really needed are Observables that are asynchronous but entail no time delay beyond a cycle of the Javascript event loop. These aren’t provided by Angular but are recommended in the Angular testing tutorial and are immensely beneficial.

import { defer } from 'rxjs';
export function asyncData<T>(data: T) {
   return defer(() => Promise.resolve(data));
}
export function asyncError(error: any) {
   return defer(() => Promise.reject(error));
}

asyncData returns an Observable that yields a single value on the next iteration of the Javascript event loop. So it is a true asynchronous Observable, but no actual time delay occurs. asyncError works similarly but makes the Observable yield an error. asyncError is used for testing error path of Observable subscribers.
Now, we just need to tell Angular that our unit test is in the asynchronous context and can use the whenStable() method of ComponentFixture to have code called when all outstanding asynchronous work has been completed. The general outline of an asynchronous unit test:

it('should do something after loading data from the network', async(() => {
   // Initial synchronous code
   fixture.whenStable().then(() => {
   // Code called after asynchronous work is done
   });
}));

Angular provides an alternative approach for asynchronicity in what is called the Fake Asynchronous Context. It works by simulating asynchronicity within the unit test. A tick function is provided to programmatically advance time. The outline for this kind of test is:

it('should do something after loading data from the network', fakeAsync(() => {
// Initial synchronous code
tick();
// Code called after asynchronous work is done
}));

This is arguably more readable than the true asynchronous style, but unfortunately is limited in terms of when it can be successfully used.

Now What?

Now’s the time to head over to my angular-unit-test-demo on github and check out the examples. It’s just a demo, but the examples are realistic top-flight unit tests similar to those I’ve written for clients. Specifically, you should check out:

Anyone looking to begin unit testing in Angular should expect to spend a few hours going through the Angular testing tutorial for starters. I realize from my own extensive experience helping developers learn unit testing that without prior unit test experience, adoption can be downright overwhelming.
If your organization is using Angular and needs help adopting unit tests (or with any other aspect of development), please reach out to us and take advantage of our extensive experience building rock-solid customer engagement applications.

Share on

linkedin sharing button twitter sharing button

Ready to get started?

Enter your information to keep the conversation going.
Location image
4 Sentry Parkway East, Suite 300, Blue Bell PA, 19422

Email Image

info@anexinet.com

Phono Image610 239 8100


Location Image4 Sentry Parkway East, Suite 300, Blue Bell PA, 19422
Phono Image610 239 8100