Advanced shallow-render testing for Angular components — part 2

Brandon Domingue
4 min readDec 7, 2018

--

Totally unrelated picture of St. Mary lake. It’s nice though, right?

This is a continuation of Testing Angular Components With shallow-render

You already know how to use shallow-render right? In this article, we’ll go over some of the more advanced techniques. I’ll explain along the way how these techniques can help tighten up your test code and eliminate even more boilerplate.

Render without the HTML Template

In the initial release of shallow-render you could test your components by typing out the same HTML you would in your template. This can be nice to serve as an example of how to use the component in your specs:

You can see that once you need more advanced bindings, we are mimicking the template/controller model in our test by specifying the HTML template to render, along with scope variables in the bind object passed into the renderer.

This works well, but we could simplify this quite a bit more by eliminating the template and just specifying the bindings.

This is my go-to pattern. It allows you to ignore the Angular Template language altogether but we keep type-safety because the bind argument must match types with the component’s inputs.

You may have noticed that in the second test, we’re not sending in a click handler. When shallow-render renders your component, it inspects the metadata and automatically spys on event emitters for you. This means to determine whether events are emitted properly, you can just assert on the emitter to have been called with the correct outputs. This is slightly sleeker than testing with templates and avoids a few scenarios that may make tests easier to read:

Pros:

  • Less code! (no need to specify event bindings)
  • Requires binding properties to match names with the component properties which can help avoid confusing a binding name with a component property.

Cons:

  • Sometimes having the HTML in your spec to serve as an example is desirable so other devs can see the usage at-a-glance.
  • Does not support rendering projected content or directives.

Testing Directives

Directives in Angular are very similar to components; so-much so that they are often referred to as the same thing in the Angular codebase. Shallow supports testing directives pretty easily.

Note that because directives must be attached to an existing HTML element, you must specify the HTML template to the renderer. Also notice that the element that is returned from the render is the div that the directive was attached to in the HTML.

Using Replacement Modules

A common pattern in the Angular community is to provide a test module that represents a substitution for a module designed specifically for tests. Angular does this quite often, here’s the HttpClientTestingModule that is used to substitute the a dummy requestor for HttpClient instances.

We can use these in our tests by “replacing” the original module with the testing module.

shallow.replaceModule(HttpClientModule, HttpClientTestingModule)

Here’s an example of a test using the testing module:

There’s a lot going on here. Much of the complexity here comes from the fact that testing HttpClient requests in Angular is a little verbose. Here are some notes (in the order you will read them from the test).

  • We use fakeAsync and async in our tests. fakeAsync allows us to manually control the flow of promise chains in our component.
  • We use shallow’s get method to pull injectables from the module. (we also have to cast the HttpTestingController because Angular used an abstract class).
  • We setup our HttpClient mocks and then tick. This resolves the request promise chain in the component constructor which in-turn updates the labelText property on the component.
  • Since we’ve triggered a UI change, we must detectChanges before we make assertions on the component.

Note: This is a contrived example intended only to provide some examples of testing with shallow-render. In a real-world application, I would always recommend wrapping calls to HTTP endpoints in a Service Class that encapsulates the HTTP communication. It’ll make your app and testing much easier!

Testing with the RouterModule

Testing apps that use the Angular Routing module is a hot-topic on the shallow-render issues list. With some of the recent additions in shallow-render it’s pretty straight forward now. Here we find again that Angular has provided us with a testing module for routing (RouterTestingModule). We just need to replace the original RouterModule with the test version and make sure our test module is configured per Angular’s docs.

This is the simplest form of testing with routes. You may even supply the RouterTestingModule replacement globally by using alwaysReplaceModule so you don’t have to remember to replace it in each spec.

Using ViewChild/ContentChild Selectors

Angular allows you to create components that play well together. Sometimes, an outer component may reference a child component using ContentChild selectors. Here’s an example of a list component that automatically activates the first item in the list.

Since shallow will automatically mock out the list-item component in our tests, we need to mock the ListItemComponent's activate function so we can verify that it is called for the first item in the list. We had to use the HTML Template style of rendering here so we could render children components into our ListContainer component.

Side note on isolation in Unit Tests

Notice that because this is a test for the ListItemContainer, we don’t really care what the ListItem#activate method does to theListItem because tests for that ListItem behavior should go in the tests for ListItem. Following this model of isolating the component under test will ensure that we are able to refactor the ListItem's activation behavior without unnecessarily breaking tests for the ListItemContainer. There’s nothing more frustrating than refactoring the internals of one component in a backwards compatible manner and breaking unit tests for some other components that didn’t really break!

--

--