I am trying to test Angular functional guard with Jest as a testing framework. For mocking the Router component I am using Angular RouterTestingHarness. Here is my functional guard (I can't edit or modify my functional guard):
export const guard: CanActivateFn = () => {
const service = inject(service);
const router = inject(Router);
return service.loginInfo().pipe(
map((res) => {
if (condition1) {
return router.parseUrl(pathA);
}
if (condition2) {
return router.parseUrl(pathB);
}
return router.parseUrl(Fail);
}),
);
};
To test it this is the test I wrote
@Component({ template: '' })
export class DummyComponent {}
describe('Test Guard', () => {
let service = {
getInfo: jest.fn(),
}; //mocking service
beforeEach(async () => {
TestBed.configureTestingModule({
providers: [
provideRouter([
{ path: PathA, component: DummyComponent, canActivate: [guard] },
{ path: PathB, component: DummyComponent },
{ path: Fail, component: DummyComponent },
]),
{
provide: service,
useValue: service,
},
],
});
service = TestBed.inject(service);
});
const spy = jest.spyOn(service, 'loginInfo');
it('test1', async () => {
spy.mockReturnValue(of(mockLogin()));
await RouterTestingHarness.create(PathA);
const url = TestBed.inject(Router).url;
expect(url).toEqual('PathA);
});
});
I am getting error
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
I have tried using jest --watch and tried to see which step is causing the memory leak, as per my finding mock of service, execution of steps until RouterTestingHarness is working. After executing RouterTestingHarness it's stuck and Jest ends after a long time with this error.
How can I fix this issue? What is the step causing data leak?
First of all, I don't know what happens inside
mockLogin(). For the purpose of this answer, I assume it returns some dummy data and then it is being compared in Guard. In this exampleloginInfo()method returns just a simple string:I did some adjustment to the guard itself. Please note, if the first condition is met, we just pass a true value to let the router navigate. No need for another
router.navigateByUrl()call. Since I used switchMap here instead of map, we have to return either Observable or Promise. That's why I exchangedrouter.parseUrl()torouter.navigateByUrl(), which returns Promise by default.SwitchMap operator automatically completes inner observable and prevents memory leaks.
Now, coming back to the test setup. I moved mockService declaration outside
describe()scope:Then I declared three independent dummy components, for better test results:
Then, my
describe()function looks like below. I moved spy declaration insideit()block and made mocking an one-liner. I created RouterTestingHarness in one step, and triggered guard later withnavigateByUrl()method. If you pass a second argument into this method, which won't be rendered it will throw an error. It works like an assertion. If you always expect one instance of components in each test you can pass it.