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.

Number of 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:

  1. More isolated testing. We want to test the services and components in question, not their dependencies.
  2. Strongly typed template checking. If a child component definition changes, we want to ensure that dependent unit tests are breaking.
  3. 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.
  4. 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:

  1. Add the child component to declarations array
  2. Use NO_ERRORS_SCHEMA to ignore the child component
  3. Manually mock/stub the child component
  4. 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:

All 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:

Adding ChildComponent fixes the test, at the expense of test isolation

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 ChildComponent?

Let’s add Router as a dependency to ChildComponent

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 NO_ERRORS_SCHEMA :

Adding NO_ERRORS_SCHEMA instructs the compiler to ignore ChildComponent

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 <child> tag?

<child> intentionally misspelled as <chidl>

…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 @Input and @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:

Instead of ChildComponent having hardcoded names of children, we’ve made the child name an input. We’ve also added selected as an input: if child equals selected , we show a ❤️ next to that child (what better way to promote healthy sibling rivalry than choosing a favourite child?).

Output of the above code

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:

Creating a stub component to use in place of the real component when testing

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 ChildComponent tests).

So, let’s write tests to ensure the inputs and outputs are behaving as expected:

Stubbing ChildComponent makes it easy to write detailed tests

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):

An example of a component with many inputs and outputs

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 childName ?

renaming child to childName

…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 MockComponent function:

Mock ChildComponent using MockComponent

We’ll also need to change our helper function to return ChildComponent instead of ChildComponentStub :

We’ll now get a bunch of compiler errors in our tests complaining that Property ‘child’ does not exist on type ‘ChildComponent’. So rename child to 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>")

As 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 childName instead:

And just like that, all our tests are passing again.

Conclusion

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