Testing Angular components with shallow-render — part 1

This article is part-1 of 2. Looking for more advanced stuff, check out part-2!

I suspect that if you’re reading this article, you’ve been through the trenches of testing components in Angular. If that’s you, I feel your pain. Testing components in Angular is HARD but it doesn’t have to be. Good answers for testing Angular components are hard to come by. I’ve talked to other devs about this and I hear a lot of confusion.

  • OMG, my TestBed setup is more complicated than the component I’m testing? What am I doing wrong?!
  • My component already lives in a module, why do I have to (re)create a module in every spec?
  • NO_ERRORS_SCHEMA is designed to prevent error reporting in templates. Don’t I want to know about errors in my tests? Isn’t that the point of writing tests?
  • I’m always creating a test host component in my test and they’re not always easy to work with. Can I avoid this somehow?
  • Mocking can get complicated when I use provider stubs. Can I stub over my stub? Is there a better pattern?
  • I started writing component stubs for testing, they’re not really type-safe Sometimes, using stubs makes my tests pass when they should fail. Now I have hundreds of component stubs and they’re hard to maintain. This is getting out of hand!
  • With all these problems… I find that my tests aren’t giving me the confidence I hoped they would. Is it testing in Angular really worth it?

When I started with Angular, I was a bit surprised that these problems hadn’t really been addressed by the community. Let’s solve them.

Enter shallow-render. The goals are simple:

  • Eliminate boilerplate
  • Encourage unit-testing best-practices
  • ABTS (always be type-safe)

Let’s dive in. First, here’s an example of a test for a reasonably standard component. It uses some pipes, directives, and other components and a service.

Whew!!! That was a lot of boilerplate. Here are just some of the issues:

  • Our TestBed module looks very similar if not identical to the realNgModule I've already added MyComponent to. Total module duplication.
  • Since I’ve duplicated my module in my spec, I’m not actually sure the real module was setup correctly.
  • I’ve used REAL components and services in my spec which means I have not isolated the component I’m interested in testing.
  • This also means I have to follow, and provide all the dependencies of those real components to the TestBed module.
  • I had to create a TestHostComponent so I could pass bindings into my actual component.
  • My TestBed boilerplate code-length exceeded my actual test code-length.

Now, let’s look at a shallow-render version of the same tests:

Here’s the difference:

  • Reuses (and verifies) MyModule contains your component and all its dependencies.
  • All components inside MyModule are automatically mocked identical inputs/outputs. This is what makes the rendering "shallow".
  • The tests have far less boilerplate which makes the tests easier to follow.
  • In the first test, we decided to use HTML to render the test-component IN THE SPEC where it’s easy to find. Each spec may have different templates, no problem.
  • In the second test, we chose not to use HTML, we just went straight for the bindings. The bindings here are type-safe and the output property’s emit function was automatically spied on.

So, how do we do this? First let’s look at the test setup. It’s simply:

new Shallow(MyComponent, MyModule)

Now, what happens behind the scenes is that we collect your test setup options but we do not render just yet, this is just the setup. Then, in the test we have:

shallow.render('<my-component linkText="my text"></my-component>)

At this point, we take all the setup that’s been collected up to this point and collapse it all into a TestBed test module behind-the scenes. This gets a little complicated, but it basically boils down to:

  • Setup a test module by mocking everything inMyModule *except* for MyComponent because that’s what we’re testing.
  • Create a test-host component and inject the template HTML from the render command.
  • Render the component, detect changes, and hand it back to the test.

Obviously, that’s just for a simple test case. Things can get pretty complicated in Angular so let’s zoom in on a particularly troublesome section of testing, .

Mocks

Sometimes, we need to mock a service. Angular suggests using test doubles but with shallow-render, mocking is built-in.

The first argument to the mock method is the thing you wish to mock, the second argument is a Partial<ThingToMock> so your mock properties and methods are type-safe. Also, any methods you place on your mock will be spy’d on automatically in case you need to make assertions on them.

It’s also common practice to setup your mocks so that all the tests in the file use the same mock(s). That’s a pretty easy tweak, just move that mock to the beforeEach block and you’re done.

If a few of your tests needs to override the default mocks, that’s ok, just mock it again and the last-mock-wins.

If you’re paying close attention here, you’ll notice that calls to mock are chain-able. You can mock as many things as you like in the chain, then just call render() at the end. You may also mock multiple methods/properties on a class with a single call to mock().

There’s a pretty basic 101 course but If you would like to learn more:

I‘m pretty passionate about this stuff so if you have any questions, drop a message in the issues section on the github project.

Happy testing!

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Step-By-Step Guide to Creating a Twitter Clone With React and Supabase — Part 3

How to build your own Notes Chrome extension

ES6 module import on browser-side

Understand DateTime converting on python

Building a transcript editor for the web

User editing some words and speaker names in our transcript editor

What is “this” in JavaScript?

Distributed Download

Fantastic Web Elements and how to find them — a guide to locate web elements for testing and web…

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Brandon Domingue

Brandon Domingue

More from Medium

Share Interfaces in Angular and NestJS with Nx

Building a MicroFrontend setup using Angular 12 — Part 1: The Project Setup

Typescript Tutorials: Webpack Typescript Configuration

Detect Active Users in Angular Using NestJs and Socket.IO