shallow-render
shallow-render copied to clipboard
Problem with mocking of RouterModule (Angular 14 + Jest)
In the process of updating my project to angular 14, in which I am using Jest and Shallow-render for unit tests, I noticed a problem with mocking of RouterModule. Unit tests failed with the following error message.
AppComponent
✕ should match snapshot (11 ms)
● AppComponent › should match snapshot
MockOfRouterOutlet does not have a module def (ɵmod property)
11 |
12 | it('should match snapshot', async () => {
> 13 | const { fixture } = await shallow.render();
| ^
14 |
15 | expect(fixture).toMatchSnapshot();
16 | });
at transitiveScopesFor (node_modules/@angular/core/fesm2020/core.mjs:24497:11)
at Array.forEach (<anonymous>)
at Array.forEach (<anonymous>)
at Array.forEach (<anonymous>)
at src/app/app.component.spec.ts:13:39
at src/app/app.component.spec.ts:12:42
● AppComponent › should match snapshot
MockOfRouterOutlet does not have a module def (ɵmod property)
at transitiveScopesFor (node_modules/@angular/core/fesm2020/core.mjs:24497:11)
at Array.forEach (<anonymous>)
at Array.forEach (<anonymous>)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 4.101 s, estimated 13 s
Minimal reproduction of the problem
You can reproduce the issue by running ng test
for the project in my repo where you find a simple Angular 14 project with Jest 28 (added with @angular-builders/jest) and Shallow-render 14.
npm i
ng test
I had the same issue and saw in the example test that they used RouterTestingModule instead of RouterModule. Adding a global replacement fixed it for me:
Shallow.alwaysReplaceModule(RouterModule, RouterTestingModule);
Thank you @mfrey-WELL for your comment. I know about using RouterTestingModule instead of RouterModule, however it dosn't work for all tests in my project. For example when I subscribe to Router events in a component and want to mock these events in a test, it's not doable with RouterTestingModule or at least I have no idea how I could do it 😉
Is your error gone with the line alwaysReplaceModule(RouterModule, RouterTestingModule)
@Mimajka ?
For testing Router events, you could inject a custom Mock - something like:
it('should do something on NavigationEnd event', async () => {
const mockEvent = new NavigationEnd(...);
const mockRouter = { events: of(mockEvent) };
const { inject } = await shallow.provide({ provide: Router, useValue: mockRouter }).render();
....
});
});
@getsaf Brandon, did you see this one? we're hitting up against it as we migrate to Angular 14. could you take a look? thx!
I've never tried to write any specs against the non-test RouterModule
. I would guess that things wouldn't go well w/out manually providing some suitable mocks for it (or using the RouterTestingModule
as @mfrey-WELL suggested).
You may want to try avoiding mocking the actual RouterModule
and RouterOutlet
with
shallow.dontMock(RouterModule, RouterOutlet)
If for some reason this doesn't work and you can't use Angular's RouterTestingModule
, @floisloading's solution sounds good too.
If neither of those solutions work, you may need to try a TestBed-only spec.
fast-follow:
I did just try modifying the routing tests in the shallow-render
repo and it worked:
describe('component with routing', () => {
let shallow: Shallow<GoHomeLinkComponent>;
beforeEach(() => {
shallow = new Shallow(GoHomeLinkComponent, GoHomeModule).dontMock(
RouterModule,
RouterOutlet,
);
});
it('uses the route', async () => {
const { fixture, find, inject } = await shallow.render();
const location = inject(Location);
find('a').triggerEventHandler('click', {});
await fixture.whenStable();
expect(location.path()).toMatch(/\/home$/);
});
});
For us it didn't work to provide a mock for Router
using shallow.provide({ provide: Router, useValue: mockRouter })
as suggested by @floisloading. Because when adding dontMock(RouterModule)
e.g. the RouterLinkWithHref
directive seemed to need some of the other methods on Router
(TypeError: Cannot read properties of undefined (reading 'subscribe') at new RouterLinkWithHref
). If we don't add dontMock(RouterModule)
we get errors like described above: MockOfRouterOutlet does not have a module def (ɵmod property)
. Similar problems for using replaceModule(RouterModule, RouterTestingModule)
.
But we could fix the problem by only mocking a single method on the Router
like this:
const rendering = await shallow
// [...] mocks etc. for router unrelated things
.dontMock(RouterModule, ActivatedRoute)
.provide({provide: ActivatedRoute, useValue: {
data: activatedRouteData.asObservable(),
queryParams: activatedRouteQueryParams.asObservable()
}})
.render();
router = rendering.inject(Router);
spyOn(router, "navigate").and.callFake((commands: any[], extras?: NavigationExtras) => {
activatedRouteQueryParams.next(extras?.queryParams || {});
return Promise.resolve(true);
});
This looks solved :-)