Advanced shallow-render testing for Angular components — part 2
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
andasync
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 theHttpTestingController
because Angular used an abstract class). - We setup our
HttpClient
mocks and thentick
. This resolves the request promise chain in the component constructor which in-turn updates thelabelText
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!