Towards Better Testing In Angular. Part 1 — Mocking Child Components
Tips and best practices on how to build better, more robust Angular applications
I remember back to my first job out of university, as a wide-eyed Mechatronics graduate. I managed to score a job as a junior developer, despite having only completed 2 software subjects during my degree.
I was hired into a medium sized company to take over development and maintenance of a mature, complex piece of software. The software was used to capture video based on triggers from other systems. It was written in C++ and used ffmpeg as its underlying technology. We had a decent sized QA team, and at least a couple of those spent a bit of time testing this software.
However, there were precisely zero unit tests and non-existent CI. The software was plagued with bugs. Half my time was spent fixing strange and hard-to-reproduce bugs (which I’ve come to realise was an exercise in futility, as the bugs were race conditions caused by a broken threading implementation).
I’ve come a long way since those days. Over the next few years, I was fortunate enough to work with people who were strong advocate of good testing and CI processes, and I’ve internalised those attitudes. No longer do I believe that fixing bugs has to take up a large chunk of a developer’s time.
In the past year at Sky Ledge, we’ve been actively developing our Angular web app. In that time, we’ve written some 1200 front-end unit tests.
In the process of developing the application, we’ve come across a few approaches that help us write more effective unit tests in Angular, specifically to ensure:
- More isolated testing. We want to test the services and components in question, not their dependencies.
- Strongly typed template checking. If a child component definition changes, we want to ensure that dependent unit tests are breaking.
- Less coupling between components and services. Dependency Injection makes it easy to inject services, but if those services are being used all over the shop, refactoring becomes difficult.
- Prefer dumb components over smart components. This article by Jack Tomaszewski explains it beautifully. Dumb components (i.e. those that rely on inputs and outputs) are much easier to test than components dependent on multiple services.
What outcomes should you expect from these articles? I’m hoping it gives you really practical advice on how to build better, more robust Angular applications, to allow you to easily develop and refactor with confidence.
Mocking Child Components
We’ll consider 4 different approaches to testing components with child components. These are, in order of least recommended to most recommended:
- Add the child component to declarations array
NO_ERRORS_SCHEMAto ignore the child component
- Manually mock/stub the child component
- Use ngMocks to automatically mock the child component
Let’s start with a very simple example. We have an aptly named component,
ParentComponent , which includes another component,
ChildComponent , in its template:
ParentComponent is doing is rendering
ChildComponent , which in turn renders the hard-coded list of children. Let’s write some code to test this component:
When we run the above test, it fails with the following error:
Failed: Template parse errors:
'child' is not a known element:
1. If 'child' is an Angular component, then verify that it is part of this module.
2. To allow any element add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component. (" <div>I am a parent. These are my children:</div>
This is Angular’s way of telling us that it doesn’t know about
ChildComponent . What are our options here?
Solution 1. Add ChildComponent to declarations array
We can always add
ChildComponent to the list of declared components:
Sure enough, if we run the tests again, they succeed. Huzzah!
…not so fast. By doing this, we’ve broken Rule 1: Tests shalt be isolated. By declaring
ChildComponent , our tests are now dependent on it to succeed. What happens if we add a dependency to
Given that we haven’t imported the
RouterTestingModule into our unit tests, we’re not able to resolve
Router , and the test is broken once more:
Error: StaticInjectorError(DynamicTestModule)[ChildComponent -> Router]:
StaticInjectorError(Platform: core)[ChildComponent -> Router]:
NullInjectorError: No provider for Router!
While we could always import
RouterTestingModule , this is clearly an unideal solution. Changes to the internal logic of
ChildComponent shouldn’t require us to modify
ParentComponent unit tests. It’s annoying enough with 2 components — imagine how much worse it’ll be once we have 20, or 200.
Solution 2. NO_ERRORS_SCHEMA
If you did a Google search for the above error, you’ll probably come across quite a few Stack Overflow posts that recommend using
NO_ERRORS_SCHEMA. It’s also recommended as an approach in the official Angular Testing Guide.
NO_ERRORS_SCHEMA tells the compiler to ignore any elements or attributes it isn’t familiar with. Let’s modify our example to use
After doing this, our test is working again 🙂. While this is a valid and widely used solution, it’s also one that leaves us vulnerable, as it violates Rule 2: Thou shalt alert us to broken templates. What happens if we misspell the
…the test still succeeds. This isn’t what we want. Even if we spelled
<child> correctly, the tests will also succeed if we incorrectly spelled inputs:
<child [childs]="children"></child> .
This is actually the approach we initially took when starting out with Sky Ledge. However, as the project grew in size and we began refactoring code, we quickly ran into situations where we didn’t realise there was something wrong until we actually ran the app. In one case, a broken component in an infrequently used part of the app remained broken for a couple of weeks until being discovered.
Beyond that, this approach also makes it very difficult to test
@Output on child components. Let’s consider another approach.
Solution 3. Manually Mocking Components
We’ll extend our example to add inputs and outputs to
ChildComponent having hardcoded names of children, we’ve made the child name an input. We’ve also added
selected as an input: if
selected , we show a ❤️ next to that child (what better way to promote healthy sibling rivalry than choosing a favourite child?).
So, how do we test
ParentComponent now that
ChildComponent is doing a bit more work? Another approach, also recommended in the Angular Testing Guide, is to manually mock (or stub) components. We can create a dummy
ChildComponent and use that in place of the real
ChildComponent . We just need to ensure that we give it the same selector:
Now, in our fixture setup, we declare the stub component in place of the real one:
This now isolates testing of the
ParentComponent from the internal workings of
ChildComponent . For our purposes, we want to treat
ChildComponent as a black box — we want to test that we’ve passed it the right inputs and that we handle any outputs it emits correctly. What it does with the inputs, and under what circumstances it emits outputs, is irrelevant to
ParentComponent (testing of that logic should happen within the
So, let’s write tests to ensure the inputs and outputs are behaving as expected:
Great. We’ve quickly and easily tested the functionality of
ParentComponent and its interactions with
ChildComponent . So are there any shortcomings with this approach?
The first shortcoming is that its quite verbose. Writing stub components for each component we want to mock can get tiresome very quickly, particularly if the components have lots of inputs and outputs. Here’s an extreme example from Sky Ledge (there’s so many inputs/outputs due to Rule 4 — Thou shalt prefer dumb components over smart components):
Not only is it a pain in the proverbial to write a stub component for something like the above, it’s also prone to code rot (Shortcoming the Second). Let’s go back to
ChildComponent. What if we wanted to rename
child to something more descriptive, like
…our tests still succeed. We won’t know it’s actually failed until we run the application (or build it with Ahead-of-Time compilation enabled). The stub component approach requires us to remember to change the stub each time we change the real component. As you can expect, this is prone to human error and the problem exacerbates as your application grows.
Issues like these can make refactoring applications and maintaining unit tests a chore. Can we do better?
Solution 4. Mocking Components using NgMock
As it turns out, we can do better. There’s an excellent library called ngMocks which makes it trivial to create mock components (as well as directives and pipes). Instead of manually creating components, ngMocks automagically creates type-safe mocks for us, alleviating both shortcomings of Solution 3.
To get started, you’ll first need to install ngMocks:
npm install ng-mocks --save-dev
We no longer require
ChildComponentStub so feel free to delete it. We’ll rewrite our tests to use ngMocks instead. First, let’s change the test config to automatically mock
ChildComponent using ngMock’s
We’ll also need to change our helper function to return
ChildComponent instead of
We’ll now get a bunch of compiler errors in our tests complaining that
Property ‘child’ does not exist on type ‘ChildComponent’. So rename
childName and run tests again.
And now…our tests are failing 🎊!
Failed: Template parse errors:
Can't bind to 'child' since it isn't a known property of 'child'.
(" a parent. These are my children:</div>
<child *ngFor="let child of children" [selected]="selected"
[ERROR ->][child]="child" (select)="onSelect($event)"></child>")
MockComponent produces a strongly typed mock, we’re getting template errors that
child doesn’t exist as an input on
ChildComponent , exactly what we want. Let’s update
ParentComponent template to use
And just like that, all our tests are passing again.
Checkout a working demo of the final solution below:
In this article, we worked our way from a fairly primitive solution to testing sub components to one that’s quite powerful and elegant.
Check back soon for more articles in this series. Sneak peak on what might be coming up:
- Services vs Inputs/Outputs
- Better mocking of services
- Using Storybook for Angular for better component design and testing
- How to structure your Angular app using features and pages
- How to write better forms to promote testing
Any questions or feedback on the article, or requests on what you’d like me to write about next? Leave me a comment below or hit me up on twitter, @abdul_rafehi