In this article we will look at three ways to test Angular components: isolated tests, shallow tests, and integration tests.

Examples

For all the examples we will use a mail app akin to Inbox or Gmail. The application displays a list of conversations, which we can browse through. Once we click on a conversation, we can see all its messages. We can also compose new messages.

Isolated Tests

It is often useful to test complex components without rendering them. To see how it can be done, let’s write a test for the following component:

compose.html:

There a few things in this example worth noting:

We are using reactive forms in the template of this component. This require us to manually create a form object in the component class, which has a nice consequence: we can test input handling without rendering the template. Instead of modifying any state directly, ComposeCmp emits an action, which is processed elsewhere. Thus the isolated test will have to only check that the action has been emitted. “this.route.snapshot.root” returns the root of the router state, and “routerStateRoot.firstChild” gives us the conversation route to read the id parameter from.

Now, let’s look at the test.

As you can see, testing Angular components in isolation is no different from testing any other JavaScript object.

Shallow Testing

Testing component classes without rendering their templates works in certain scenarios, but not in all of them. Sometimes we can write a meaningful test only if we render a component’s template. We can do that and still keep the test isolated. We just need to render the template without rendering the component’s children. This is what is colloquially known as shallow testing.

Let’s see this approach in action.

This constructor, although short, may look a bit funky if you are not familiar with RxJS. So let’s step through it. First, we pluck “folder” out of the params object, which is equivalent to “route.params.map(p => p[‘folder’])”. Second, we pluck out “conversations”.

In the template we use the async pipe to bind the two observables. The async pipe always returns the latest value emitted by the observable.

Now let’s look at the test.

First, look at how we configured our testing module. We only declared ConversationsCmp, nothing else. This means that all the elements in the template will be treated as simple DOM nodes, and only common directives (e.g., ngIf and ngFor) will be applied. This is exactly what we want. Second, instead of using a real activated route, we are using a fake one, which is just an object with the params and data properties.

Integration Testing

Finally, we can always write an integration test that will exercise the whole application.

Even though both the shallow and integration tests render components, these tests are very different in nature. In the shallow test we mocked up every single dependency of a component. In the integration one we did it only with the location service. Shallow tests are isolated, and, as a result, can be used to drive the design of our components. Integration tests are only used to check the correctness.

Summary

In this article we looked at three ways to test Angular components: isolated tests, shallow tests, and integration tests. Each of them have their time and place: isolated tests are a great way to test drive your components and test complex logic. Shallow tests are isolated tests on steroids, and they should be used when writing a meaningful test requires to render a component’s template. Finally, integration tests verify that a group of components and services (e.g., the router) work together.