Skip to main content

How to mock ActivatedRoute in Angular tests

When we are talking about mocking ActivatedRoute, we mean a solution to provide stub params in the snapshot of ActivatedRoute which are used in the component under test.

let's assume we have the next TargetComponent component, which relies on the paramId of the ActivatedRoute snapshot.

@Component({
selector: 'target',
template: '{{ param }}',
})
class TargetComponent {
public param: string | null = null;

public constructor(private route: ActivatedRoute) {}

ngOnInit() {
this.param = this.route.snapshot.paramMap.get('paramId');
}
}

In our test as a mock ActivatedRoute, we would like to provide paramValue as paramId. To do so, we can use MockInstance.

The first step is to call a spy when someone wants to access snapshot:

// for jasmine
MockInstance(ActivatedRoute, 'snapshot', jasmine.createSpy(), 'get');

// for jest
MockInstance(ActivatedRoute, 'snapshot', jest.fn(), 'get');

The second step is to return the stub params:

// for jasmine
MockInstance(ActivatedRoute, 'snapshot', jasmine.createSpy(), 'get')
.and.returnValue({
paramMap: new Map([['paramId', 'paramValue']]),
});

// for jest
MockInstance(ActivatedRoute, 'snapshot', jest.fn(), 'get')
.mockReturnValue({
paramMap: new Map([['paramId', 'paramValue']]),
});

Profit. Now, when someone accesses snapshot of ActivatedRoute, the spy will be called, which returns a stub paramMap with the params we wanted.

RouterModule.forRoot

In situation when you want to mock a module which imports RouterModule.forRoot.

In this case, only the component under test should be kept:

// TargetModule and RouterModule.forRoot will be mocks
beforeEach(() => MockBuilder(
TargetComponent, // keep
TargetModule, // mock
));

RouterModule.forChild

In situation when you want to mock a module which imports RouterModule.forChild, you have to add RouterModule.forRoot to mocks too.

Otherwise, ActivatedRoute and other dependencies won't be available:

// TargetModule, RouterModule.forChild and RouterModule.forRoot will be mocks
beforeEach(() => MockBuilder(
TargetComponent, // keep
[TargetModule, RouterModule.forRoot([])], // mock, add here RouterModule.forRoot([])
));

Live example how to mock ActivatedRoute

https://github.com/help-me-mom/ng-mocks/blob/master/examples/MockActivatedRoute/test.spec.ts
import { Component, NgModule, OnInit } from '@angular/core';
import { ActivatedRoute, RouterModule } from '@angular/router';

import { MockBuilder, MockInstance, MockRender } from 'ng-mocks';

@Component({
selector: 'route',
template: '{{ param }}',
})
class RouteComponent implements OnInit {
public param: string | null = null;

public constructor(private route: ActivatedRoute) {}

ngOnInit() {
this.param = this.route.snapshot.paramMap.get('paramId');
}
}

@NgModule({
declarations: [RouteComponent],
imports: [
RouterModule.forRoot([
{
path: 'test/:paramId',
component: RouteComponent,
},
]),
],
})
class TargetModule {}

describe('MockActivatedRoute', () => {
// Resets customizations after each test, in our case of `ActivatedRoute`.
MockInstance.scope();

// Keeping RouteComponent as it is and mocking all declarations in TargetModule.
beforeEach(() => MockBuilder(RouteComponent, TargetModule));

it('uses paramId from ActivatedRoute', () => {
// Let's set the params of the snapshot.
MockInstance(
ActivatedRoute,
'snapshot',
jasmine.createSpy(),
'get',
).and.returnValue({
paramMap: new Map([['paramId', 'paramValue']]),
});
// // in case of jest.
// MockInstance(
// ActivatedRoute,
// 'snapshot',
// jest.fn(),
// 'get',
// ).mockReturnValue({
// paramMap: new Map([['paramId', 'paramValue']]),
// });

// Rendering RouteComponent.
const fixture = MockRender(RouteComponent);

// Asserting it got the right paramId.
expect(fixture.point.componentInstance.param).toEqual(
'paramValue',
);
});
});