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 real
NgModule
I've already addedMyComponent
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 in
MyModule
*except* forMyComponent
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:
- The code is on github: shallow-render
- Lots of documentation!
- There are LOTS of examples in the official StackBlitz project. These examples also serve as part of the test coverage for the library itself.
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!