Consider we want to test our routing configuration. This is a common case when taking advantage of route guards. Ok, so let’s do this!
Here is the catch. In order to test your routing, you are basically forced to import:
- all container components that are connected to your routes,
- all components used in templates of your container components,
- all components, directives, pipes and services that are Dependency Injected or anyhow referenced inside those components.
THE PROBLEM
So basically you need to import everything into your test module. This sucks. You may ask “is this really such a problem?” Well, in my opinion it definitely is, so since most likely your application consists of more than a single route, one directive and a pipe. To make it short: you are working with a real-word non-trivial application.
In my case, I needed a simple solution that could save me from minding each and every component in a dependency chain. My main goal was to check if canActivate guard is doing his job.
SOLUTIONS
So what we can do about it? Of course we go straight ahead and use mocks. At least we can try to since Angular does not provide any built-in way to mock a component. Again, this sucks. Fortunately, I’m not the first person to rise this issue.
Christian Nunciato’s MockComponent approach
First of all, a big shout out to Christian, who basically inspired me to write this article. We can continue with a solution suggested by Christian in his great article (you should definitely read through it). In a nutshell, Christian has created a function that produces brand new components with given set of metadata. It looks like this:
MockComponent({ selector: ‘login’ });
(Here is a github repository you can get MockComponent)
This approach just got rid of issue 2 and issue 3 from my list above in an instance! But still, we must make our way through all dependencies and create mock components for every container component attached to each route under test. So we might end up with something like this:
TestBed.configureTestingModule({
declarations: [
MockComponent({ selector: 'login' }),
MockComponent({ selector: 'home' }),
MockComponent({ selector: 'screen1' }),
MockComponent({ selector: 'screen2' }),
.
.
.
],
});
You get the idea. Moreover, you need to maintain this declarations whenever routes configuration changes. They must match, otherwise Angular Router reports an error. This might become a chore, especially in modules with many routes.
EmptyComponent approach
What I was trying to achieve, was to limit TestBed configuration to its minimum. So instead of mocking each component for TestBed, I have come up with the idea of a single Empty Component attached to each route in routes configuration. But how to accomplish this? All routes have already had their components specified after all. That’s right. But we can map it to a brand new one.
Component({})
class EmptyComponent {}
const routesUnderTest: Routes = routes.map(
(route: Route) => ({ ...route, component: EmptyComponent })
);
Just copy original route configuration and overwrite component attribute. Then use routesUnderTests instead of routes array, to configure your RouterTestingModule. Angular Router will not complain about it.
CONCLUSION
I am not suggesting my solution is better or cleaner. On the contrary, there is more code smell going around in my approach because I am messing with original route configuration. Mocking components explicitly is more declarative and easy to read.
To sum it up, if you need to mock a component that has a specific set of metadata, inputs and outputs defined — go for MockComponent approach. You will end up with readable and clean test module configuration.
On the other hand, if you are focused only on URL changing, you don’t care about component internals and just want to “make it work” — I would recommend using EmptyComponent approach. You will get slightly harder to reason about but very minimalistic setup.
And that’s it. Hope you find it useful. Choose wisely and go for what suits you the most.
Leave a comment