How to test the Angular router
Navigation and router unit testing in Angular with the RouterTestingModule
Let me be crystal clear.
I want to make sure we get this straight and certain from the get-go.
When I say testing the Angular router I don't mean that we're actually writing tests for the Angular router.
Instead, here's what I'm trying to say.
When I say testing the Angular router, I'm talking about testing Angular components, services and other pieces of our Angular application that depend on the Router service given to us by the framework.
The Angular router service gives us the ability to navigate around views as well as URL manipulation capabilities.
It's a common dependency in an Angular application.
But, when you try to write a unit test for your Angular application, you'll probably get a nasty injection error. 🔥 🔥 🔥
Like this.
NullInjectionError: No provider for Router!
So, how do you get around the null injection error when testing the Angular router?
How do you test an Angular component or service that depends on the router service?
Hang in there, I'm gonna show you how we fix this!
How to test a component that uses the Angular router
First, you might not even want to test the routing functionality that your component has.
Maybe you're trying to test a different piece of logic but when you write the test you get the injection error...
NullInjectorError: No provider for Router!
How do you fix this?
Are there any quick, simple, jazzy workarounds?
Yes buster, ya bet, there is a simple fix for this injection error.
The solution?
All you need to do is import the RouterTestingModule into your testing module like this.
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyComponent } from './my.component';
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ MyComponent ],
imports: [ RouterTestingModule ]
})
.compileComponents();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
Cool. Eh? 🤓
But what if we want to test the routing functionality?
How do we write a test that verifies that we've actually navigated somewhere else when we use the Router?
How to test navigation
There's more than one way to test that our component or service is using the Router to properly navigate.
But I'll show you the most generic and easiest approach.
This will be a unit test - meaning we'll mock the router functionality.
It's not exactly the simplest way of doing this but it should work for every testing scenario you'll face in the wild Angular jungles.
Ready buddy?
Here's how we do it.
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Router } from '@angular/router';
import { MyComponent } from './my.component';
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
const routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']);
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ MyComponent ],
providers: [
{
provide: Router, useValue: routerSpy
}
]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should navigate to the details page', () => {
component.showDetails();
const navArgs = routerSpy.navigateByUrl.calls.first().args[0];
expect(navArgs).toEqual("/");
});
});
Buddy? You still with me?
Great!
So what did we just do?
We created a mock for the router dependency and used it instead of the RouterTestingModule.
Then, we called our function that uses the navigateByUrl
ability in the router service. But since we created a mock to replace the real router service, our mock function was called.
And finally, we verified that our mock had been called with the expected parameter to ensure that navigation to the right place is actually happening.
Conclusion
So, should you mock the Router dependency?
Absolutely, that is, if you're writing unit tests.
If you want to get fervidly-serious about testing the routing functionality of your application then use e2e tests.
But otherwise, a mock or spy object is the way to go.
And that, my friend, is how to write unit tests for the pieces of your Angular application that depend on the Angular router.
Questions? Comments? Don't hesitate to reach out.
Angular Developer
P.S. - In case you're a skimmer (like me) here are a few things this blog post will teach you...
- How to test components, services, etc... that depend on Angular's router service.
- Show you how to avoid the null injection that can happen when attempting to write your unit tests.
- How to create a spy object for the Router.
- And why testing the Angular router or the Angular routing module isn't an icky job. 😁