Easier Angular Unit Testing
Tags: angular, testing, webdev, typescript
Published: 2019-06-15 23:59:35

__MAY 2020 NOTICE__: While the tooling presented in this article is (mostly) great, I no longer agree with the testing strategy as written. Yes, testing implementation details can increase your coverage and turn some green lights on, but it is usually not particularly valuable in validating the correctness of your code. Please read [here](https://testing-library.com/docs/guiding-principles) for more details, and be sure to check out what I now recommend: [Angular Testing Library](https://testing-library.com/docs/angular-testing-library/intro).

---

In this article, we're going to learn how to level-up our Angular unit testing experience with an alternate testing toolkit. I'll walk you through how to switch your project from Karma to Jest, how to set up Wallaby in your Angular project, and how to test your components and services with a combination of Spectator and shallow rendering.

__Warning:__ My unit testing approach is opinionated. As with anything opinionated in software development, there are lots of folks who [agree](https://hackernoon.com/why-i-always-use-shallow-rendering-a3a50da60942) and [disagree](https://kentcdodds.com/blog/why-i-never-use-shallow-rendering). My role is to present the approach that has worked well for my teams and projects, and to give you a roadmap of how to get started with it yourself.

---

### Intro

> Hard to test code is hard to love code.

Testing your applications with the toolkit provided by the Angular CLI is **hard**. With a little bit of legwork, we can make some dramatic improvements to our testing dev experience, which might include:

- 🔥 Tests can potentially run much faster!
- 👀 We can automatically see when tests pass or fail in our editor without even needing to run tests in the command line!
- ✨ We can easily only run tests that would be impacted by our upcoming git commit!
- 🥳 We can write tests with much less boilerplate, helping us move faster as a team or engineering organization!

This is all possible by leveraging alternative tooling in our Angular CLI projects. Let's get started.

---

### 👉 Improvement #1: Use Jest as a test runner.

![Jest logo](https://i.imgur.com/nF8eMPG.png)

[Jest](https://jestjs.io), Facebook's speedy Node-based test runner, can dramatically improve the testing dev experience of your Angular app in a few key ways: 

- Tests are executed in parallel, not synchronously, which can result in much faster test execution for large projects.
- Tests are run directly in your command-line in a simulated DOM environment. You no longer need to launch a browser instance to run your tests.
- Tests impacted by your current git diff can be easily and intelligently isolated.

By switching to Jest, we'll no longer have a need for Jasmine in our project. This is nothing to worry about though! Jest's API is *almost* identical to Jasmine's.

While there are [schematics to instantly switch a project over to Jest](https://github.com/briebug/jest-schematic), at the time of this writing they have [known issues](https://github.com/briebug/jest-schematic/issues/12) with Angular 8 and don't yet properly reconfigure your `ng test` command. Because of these woes, we'll convert our Angular 8 project manually.

#### Jest Setup Guide

__1. Remove all Karma dependencies and configuration files.__

Execute the following commands in your workspace's root folder:

`rm karma.conf.js src/test.ts`

`npm rm -D karma karma-chrome-launcher karma-coverage-istanbul-reporter karma-jasmine karma-jasmine-html-reporter`

__2. Remove dead _test.ts_ file references in your tsconfigs.__

In _tsconfig.app.json_ and _tsconfig.spec.json_, remove the reference to _"src/test.ts"_.

__3. Add Jest dependencies.__

Execute the following command in your workspace's root folder:

`npm i -D jest jest-preset-angular @angular-builders/jest @types/jest`

__4. Add Jest types to your _tsconfig.json_.__

In app root, open _tsconfig.json_ and add a _types_ array to your _compilerOptions_ object:

```
"compilerOptions": {
  ...
  "types": ["jest"]
}
```

__5. Add a _jest.config.js_ in your root folder (alongside your tsconfigs).__

{% gist https://gist.github.com/wescopeland/63f0633d27f514c2dee8d72d09e6a94d %}

Note that we are using a `moduleNameMapper` object in this config. This directly correlates to your tsconfig's `paths` object. Jest will be unaware of any tsconfig paths until we specify them in _jest.config.js_.

If `paths` is unfamiliar to you, don't fret! Angular projects typically have a `CoreModule` and `SharedModule`, and it's reasonable when we import stuff from them, we'd want to reference things from those modules using `@shared/my-service` as opposed to `../../../../shared/my-service`. We set up these aliases with `paths`. If you'd like to do this now, modify your _tsconfig.json_ like so:

```
"compilerOptions": {
 ...
 "paths": {
   "@core/*": ["src/app/core/*"],
   "@shared/*": ["src/app/shared/*"]
 }
```

__6. Modify your project in _angular.json_ to use Jest instead of Karma.__

```
"projects" {
  ...
  "your-app-name": {
  ...
    "test": {
      "builder": "@angular-builders/jest:run",
      "options": {
        "configPath": "./jest.config.js"
      }
    }
  }
}
```

__7. Run `ng test`!__

![ng test running with Jest in the terminal](https://i.imgur.com/l2BQJ8n.png)
<figcaption>You should see something close to this.</figcaption>

Awesome! But what if we only want to run tests that are impacted by our current change? This can be done with:

`ng test --onlyChanged`

![ng test --onlyChanged checking against our git uncommitted changes](https://i.imgur.com/LpswjHu.png)
<figcaption>Only our uncommitted changes are being tested!</figcaption>

---

### ✅ Improvement #2: Use Wallaby.js in your editor.

![Wallaby.js logo](http://wallabyjs.com/assets/img/logoWithText.svg)

[Wallaby.js](https://wallabyjs.com/) is an integrated continuous test runner for JavaScript-based projects. It supports the most popular editors, including VSCode, Atom, Sublime Text, and Visual Studio. **Wallaby is not free**, so if cost is an issue, *it's safe to skip this section.* I have a Wallaby personal license and use it in all my projects due to the productivity boost I get from it, and for the sake of full disclosure in sharing my testing workflow, I want to also share my Wallaby configuration for Angular projects.

![Wallaby.js in action](https://i.imgur.com/0Vprbe5.gif)
<figcaption>Wallaby.js in action.</figcaption>

#### Wallaby.js Setup Guide for Angular+Jest

__1. Install dependencies.__

In your root directory, run the following command:

`npm i -D ngx-wallaby-jest`

__2. Add _wallaby.js_ in your root directory, alongside your tsconfig files.__

{% gist https://gist.github.com/wescopeland/664580611cc3e5bab252d1bfc31b99c9 %}

Notice we have some boilerplate in our `setup` function that tells Wallaby how to interpret our Jest `moduleNameMapper` (aka tsconfig `paths`) values.

__3. Add the Wallaby extension to your editor and start it.__

What you see from here will vary by what editor you're using, but you should now see pass/fail in your gutter and console logging similar to the gif above.

---

### On to writing tests...

Now that we have our tooling set up, let's talk about the actual contents of our tests.

Most Angular tests are written with the help of `TestBed`. In fact, the most popular libraries that help eliminate Angular testing boilerplate still rely on `TestBed` under the hood.

Not only is a large amount of `TestBed` boilerplate required to set up a test's dependency injection (none of which is reusable), `TestBed` can be used (or abused, depending on who you ask) to [blur the line between unit and integration tests](https://medium.com/@marko.bjelac/unit-testing-angular-testbed-considered-harmful-7e2bb8f32586). It is not unheard of for a `beforeEach` using `TestBed` to have more lines of code than all of a component's unit tests combined.

Angular unit tests will rely on `TestBed` for the foreseeable future, even if it's hidden beneath the fold by a helper library. I'll show you how to use one such helper library to abstract some of the `TestBed` heavy lifting away and make a positive shift in the boilerplate-to-test ratio in our Angular .spec files.

---

### 😎 Improvement #3: Test components and services with Spectator.

![Spectator logo](https://i.imgur.com/S9VVKgd.png)

[Spectator](https://github.com/NetanelBasal/spectator) is an Angular unit testing library built on top of `TestBed` and developed by the same folks who built Akita. It can aid with dramatically reducing your test suite boilerplate, and it provides a very clean API for writing your unit tests.

For most Angular applications I've worked with, there are two types of services: those that make HTTP calls and those that do not. We'll use Spectator to test both types.

#### Non-HTTP Injectables

Let's pretend we have a notification service in our app that wraps around the Angular Material `MatSnackBar`. It looks like this:

{% gist https://gist.github.com/wescopeland/cffaaaad033d4b2a72a7326828057843 file=notification.service.ts %}

This service has one dependency: `MatSnackBar`, and one function:  `notify`. Let's tackle the spec with Spectator.

First, install some needed dependencies:

`npm i -D @netbasal/spectator ng-mocks`

A spec file for this might ultimately look like:

{% gist https://gist.github.com/wescopeland/cffaaaad033d4b2a72a7326828057843 file=notification.service.spec.ts %}

**Woah**, there is a lot going on here. Let's talk about everything happening in the initialization of our test suite, step by step.

```typescript
let snackBar: SpyObject<MatSnackBar>;
```

How `MatSnackBar` works is irrelevant to us. Injecting the real `MatSnackBar` into this test suite would break unit isolation and **would be considered an anti-pattern**. There are a few approaches to maintaining unit isolation in instances like this, and my favorite is to make each of the injectable's dependencies into a `SpyObject`. `SpyObject` gives us the means to easily test that `NotificationService` is calling `MatSnackBar`'s functions with the expected arguments, regardless of what `MatSnackBar` might do with those arguments once they're received.

```typescript
let spectator: SpectatorService<NotificationService> = createService({
  service: NotificationService,
  mocks: [MatSnackBar]
});
```

Here's where the magic starts happening. We use `createService` to stand up a `SpectatorService` instance for `NotificationService`. We don't have to worry about manually stubbing or mocking out `MatSnackBar`, the magical `mocks` array takes care of that automatically for us.

We can now condense our `beforeEach` contents to one line of code:

```typescript
beforeEach(() => {
  snackBar = spectator.get(MatSnackBar);
});
```

Before every test, (re)initialize our `SpyObject`. Simple! Now, time to write some tests!

```typescript
it('exists', () => {
  expect(spectator.service).toBeDefined();
});
```

*No matter what you're testing*, it's never a bad idea for the first test in your suite to be a sanity check. This may wind up saving you a lot of time down the road, and combined with Wallaby you can instantly see in your editor if you forgot to mock a dependency. Note that we use `spectator.service` to access our `NotificationService` instance.

```typescript
it('can pop open a snackbar notification', () => {
  spectator.service.notify('mock notification');
  expect(snackBar.open).toHaveBeenCalledWith('mock notification', 'CLOSE', {
    duration: 7000
  });
});
```

Digging a little deeper, we call our `notify` function and ensure it tells `MatSnackBar` to do what it's supposed to do. Notice we don't need to write any code for `spyOn` in order to use `toHaveBeenCalledWith`. Our `SpyObject` boilerplate takes care of that for us automagically. This is a test that is both understandable and easy on the eyes.

#### Injectables with HTTP calls

We need to grab some generic entities from our back-end. Here's what a very simple HTTP service injectable (with no error handling) might look like:

{% gist https://gist.github.com/wescopeland/5c706ba8ec2d3b43666e39f19e33a54e file=awesome-entity.model.ts %}

{% gist https://gist.github.com/wescopeland/5c706ba8ec2d3b43666e39f19e33a54e file=entity-data.service.ts %}

Our test needs to validate that the `GET` call is actually being made. With Spectator, this is pretty easy!

{% gist https://gist.github.com/wescopeland/5c706ba8ec2d3b43666e39f19e33a54e file=entity-data.service.spec.ts %}

Hopefully this is a little more straightforward than our previous test suite, but it doesn't hurt to break it down anyway.

```typescript
const httpService: () => SpectatorHTTP<EntityDataService> = createHTTPFactory<
  EntityDataService
>(EntityDataService);
```

This is the toughest part of the suite to unpack, and you really don't *need* to understand what's happening here, other than knowing this is a recipe for building an `httpService` that you can use to test this Angular injectable. 

`SpectatorHTTP` is a fancy wrapper around a few of the classes the Angular team provides for testing HTTP API calls.

```typescript
it('exists', () => {
  const { dataService } = httpService();
  expect(dataService).toBeDefined();
});
```

Like our previous test suite, it's never a bad idea to start with a sanity check. `dataService` is the actual `EntityDataService` instance that is generated by `createHTTPFactory`.

```typescript
it('can get entities from the server', () => {
  const { dataService, expectOne } = httpService();

  dataService.getEntities().subscribe();
  expectOne('/api/entities', HTTPMethod.GET);
});
```

This should be very readable. We get our service instance and `expectOne`, an assertion that accepts a URL path and an HTTP method. We subscribe to the observable our `getEntities` function returns in order to make the API call, then we use `expectOne` to assert that the call itself was made and sent correctly.

#### Non-HTTP injectables that depend on HTTP injectables

For a third and final service-layer example, get ready for injectable-ception as we test an injectable that uses our API service from the previous example. This service will be responsible for interacting with our API service, and then sending the API response to our state management system's store. *In other words, this injectable will have two dependencies.* See below:

{% gist https://gist.github.com/wescopeland/04e5c398fc340ea6b79b06df3b5e8dec file=entity.service.ts %}

This service directly interacts with `EntityDataService` and `AwesomeEntityStore`. When we get a response from our API call, we send the entities to the store, presumably to be consumed by some component somewhere. Thankfully, testing this with Spectator is fairly simple!

{% gist https://gist.github.com/wescopeland/04e5c398fc340ea6b79b06df3b5e8dec file=entity.service.spec.ts %}

Like the previous tests, let's walk through this step by step.

```typescript
let dataService: SpyObject<EntityDataService>;
let store: SpyObject<AwesomeEntityStore>;

let spectator: SpectatorService<EntityService> = createService({
  service: EntityService,
  mocks: [EntityDataService, AwesomeEntityStore]
});
```

Things should start feeling more familiar now! We declare some `SpyObject` instances for our two dependencies, and pass those same dependencies into the `mocks` array.

```typescript
beforeEach(() => {
  dataService = spectator.get(EntityDataService);
  store = spectator.get(AwesomeEntityStore);
});
```

Initialize those `SpyObject` instances so we can use them in our tests. Since they're in the `beforeEach`, they'll reinitialize after every unit test.

```typescript
it('can try to get all the awesome entities and put them in the store', () => {
  dataService.getEntities.andReturn(
    of(
      [{ id: 1 }]
    )
  );

  spectator.service.getAllEntities().subscribe();

  expect(store.set).toHaveBeenCalledWith(
    [{ id: 1 }]
  );
});
```

Here's where the magic happens! We tell `dataService` that if the `getEntities` function is called, it will return an observable with an array containing an `AwesomeEntity`. We can then monitor `store`'s `set` function, and ensure the correct argument is passed.

This is all awesome, but things really start to get interesting when we move over to testing our components. A typical application has [two different types of components](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0): presentational (dumb/stateless) components and container (smart/stateful) components. Spectator makes testing both types pretty easy.

#### Presentational components

Tests for presentational components are some of the easiest to write with Spectator, but the recipe for testing components is overall a little different than testing services.

In the example below, we have `PresentationalButtonComponent`. It has one input: a label, and one output: a string emission.

{% gist https://gist.github.com/wescopeland/d97967ad36bba5ee63af788af27b5bfb file=presentational-button.component.ts %}

If you're not using presentational components in your Angular app, now would be a great time to consider them! It is an ideal design pattern to have root components managing state, while their templates assemble your app views using small stateless presentational components. One of the benefits of this pattern is these stateless components are very easy to test. Take a look:

{% gist https://gist.github.com/wescopeland/d97967ad36bba5ee63af788af27b5bfb file=presentational-button.component.spec.ts %}

Another **woah**. It's very easy to stand up this test using Spectator! Our total boilerplate is down to a mere seven lines, and our tests are all very readable thanks to Spectator's super-clean API! Let's do another walkthrough.

```typescript
let spectator: Spectator<PBComponent>;
const createComponent = createTestComponentFactory<PBComponent>({
  component: PBComponent
});

beforeEach(() => {
  spectator = createComponent();
});
```

This is the recipe to stand up any presentational component test suite with Spectator. The only thing that `createTestComponentFactory`'s options object needs is a `component` value with your component class specified. Then, `beforeEach` test, run `createComponent()`.

```typescript
it('exists', () => {
  expect(spectator.component).toBeDefined();
});
```

Our sanity check. The component class instance is accessed through `spectator.component`. There are other things available through our `spectator` variable too, such as `spectator.fixture` for the component fixture.

```typescript
// JSDOM
it('renders a button with a default label if no label is given', () => {
  expect(spectator.query('button')).toHaveText('Submit');
});
```

In this test suite, I have two JSDOM tests and one test against the component class. Here, we use `spectator.query` to find a `<button>` element in the template, and assert that it should have the text "Submit".

There are those who believe the JSDOM tests would be better handled by tools such as Protractor or Cypress and these two specs have no place in the unit test suite. My opinion on that is outside the scope of this article. I'm offering the JSDOM tests as an example if this is an approach you'd like to take. Remember, your JSDOM tests will have no impact on code coverage *because they are testing against a simulated DOM rather than your TypeScript code*.

```typescript
// JSDOM
it('renders a button with an input label if one is given', () => {
  spectator.component.buttonLabel = 'Mock Label';
  spectator.detectChanges();

  expect(spectator.query('button')).toHaveText('Mock Label');
});
```

There's a little more going on here, but it's still not too tough to follow. First, we provide a value to our `buttonLabel` input. Then, we kick off `detectChanges()` so it'll show up in the DOM (otherwise the value will still be "Submit"). After the test arrangement is done, we can run our `toHaveText` assertion just like the previous test!

```typescript
// Component Class
it('can emit a message', () => {
  const pressEmitSpy = spyOn(spectator.component.press, 'emit');

  spectator.component.handleButtonClick();
  expect(pressEmitSpy).toHaveBeenCalledWith('Submit was pressed!');
});
```

As the comment states, this is a test against our actual TypeScript code in the component class. We aren't simulating a button press and ensuring `handleButtonClick()` was called (this would start crossing into integration test territory), we're just making sure than *when* it is called, it emits the message we'd expect.

To do this, we `spyOn` our `EventEmitter`'s `emit` function, and use the `toHaveBeenCalledWith` assertion like we did with our injectable tests.

#### 😱 Container + stateful components

Last but not least, we'll write some tests for a stateful component. I've found stateful component unit tests to be the most stressful tests to write for Angular apps, but Spectator greatly simplifies things. Check it out:

{% gist https://gist.github.com/wescopeland/8726b9ef6583cec597ce45ae56426326 file=stateful.component.ts %}

Our stateful component is deceptively simple. Two key takeaways here:
- We're injecting a service: `AwesomeEntitiesQuery`, which is called in the `ngOnInit()`.
- Our template uses the presentational component from the previous example with the `<app-presentational-button>` tag.

It would normally take a lot of boilerplate to set up a test suite for this component, but Spectator can make this very clean for us. Here's the spec:

{% gist https://gist.github.com/wescopeland/8726b9ef6583cec597ce45ae56426326 file=stateful.component.spec.ts %}

Our friends `SpyObject` and `mocks` have returned, but we also have a new addition to the fray: `shallow: true`. Time for another walkthrough!

```typescript
let query: SpyObject<AwesomeEntitiesQuery>;

let spectator: Spectator<StatefulComponent>;
const createComponent = createTestComponentFactory<StatefulComponent>({
  component: StatefulComponent,
  mocks: [AwesomeEntitiesQuery],
  shallow: true
});
```

Similar to our previous tests where we had service dependencies, our stateful component does too: `AwesomeEntitiesQuery`. And just like our previous tests, we have a magical `mocks` array that takes care of everything for us.

We also see `shallow: true`. Why is this here? Well, our child component `<app-presentational-button>` in the template complicates things. We also don't actually care what this component is doing or how it works. Shallow rendering mocks child components. By taking advantage of shallow rendering, we can pretend `<app-presentational-button>` is transformed into something like an empty `div`. For a more detailed explanation of shallow rendering, the React documentation [offers a short blurb](https://reactjs.org/docs/shallow-renderer.html) on the topic.

```typescript
beforeEach(() => {
  spectator = createComponent();
  query = spectator.get<AwesomeEntitiesQuery>(AwesomeEntitiesQuery);
});
```

This is a combination of the `beforeEach` calls in our previous examples with services and components. Here, we create our component and we also set up our `SpyObject`.

```typescript
it('gets all the awesome entities on initialization', done => {
  query.selectAll.andReturn(of([]));
  spectator.component.ngOnInit();

  spectator.component.awesomeEntities$.subscribe(val => {
    expect(val).toEqual([]);
    done();
  });
});
```

We specify that when `selectAll()` from our service is called, it's going to return an observable that contains an empty array. We then kick off our `ngOnInit()` and make an assertion that this value actually made it into our stateful component's `awesomeEntities$` variable.

---

### 🎉 Where to go from here

If you've decided to utilize Jest, Wallaby, and/or Spectator in your projects, here are some great resources to continue on:

- 📒 [The Jest documentation](https://jestjs.io/docs/en/getting-started)
- 📒 [The Spectator documentation](https://netbasal.gitbook.io/spectator/)
- 🕵️‍♀️ Check out your app's code coverage with Wallaby by going to [https://wallabyjs.com/app/#/files](https://wallabyjs.com/app/#/files) when the plugin is running in your editor.
- 😻 Interested in the state management solution shown in the examples here? Check out [Akita](https://github.com/datorama/akita).

Thanks for reading!