Angular 17 fakeAsync tasks not running until test is cleaned up with 'resetFakeAsyncZone()'
IMeyers2020 opened this issue · comments
Which @angular/* package(s) are the source of the bug?
core, zone.js, Don't known / other
Is this a regression?
No
Description
Overview
Hey there,
I am in the progress of migrating my Angular project from v14 to v17, and also migrating my Jest from v27 to v29 at the same time. I have my project running, with no console errors / warnings, but I am struggling to get my tests to work.
I am running my tests using the fakeAsync wrapper, but am getting an issue where some of the async tasks within aren't ran (Service calls, event emitters, etc.) until after any of my expect(...)
checks fail and Angular's resetFakeAsyncZone()
runs, despite having all the necessary flush
/ tick
calls (flush()
doesn't even cause comp.ngOnInit()
to run) (this test passed before and breakpoints can show that it is still getting the same data but just not until after the expect call).
It just seems very odd that the service call will get hit after the expect fails.
One of my failing tests
it('should call Update on submit if shouldUpdate is true', fakeAsync(() => {
// arrange
comp.shouldUpdate = true;
comp.CanAlter = true;
jest.spyOn(connectionResource, 'Update').mockImplementation((c) => {
return Promise.resolve({ ...c, Id: connection.Id, Children: [] } as ApiConnection);
});
let onSubSpy = jest.spyOn(comp.onSubmit, 'emit');
// act
comp.ngOnChanges();
flush();
fixture.detectChanges();
let result = { ...comp.form.getRawValue(), ParentId: probe.Id };
let submitButton = fixture.debugElement.query(By.css('button[type="submit"]'));
submitButton.nativeElement.click();
flush();
// assert
expect(connectionResource.Update).toHaveBeenCalledWith(result, connection.Id);
let cached = coreCache.SearchMonitorableTree(coreCache.PayloadCache.Monitorables[0], 'Id', connection.Id)[0];
expect(onSubSpy).toHaveBeenCalledWith(cached); // This expect fails, and the the spied function is hit directly after
}));
What I have tried so far
-
I have tried changing the expect to
toHaveBeenCalled()
to double check that there wasn't some small change to the model somewhere, it insisted that it wasn't called, then called it right after (same problem) -
I tried using
jest.useFakeTimers()
(there were many changes to fakeTimers in the last couple Jest updates), this did not fix. -
I tried adding
tick()
andflushMicrotasks()
to see if those would have any affect, they did not. -
I tried using
runInInjectionContext(fixture.debugElement.injector, () => {...});
to wrap all my inject statements to see if those were somehow injected into the Zone incorrectly. -
I tried calling
resetFakeAsyncZone()
to see if that had any affect as that is what seems to cause it to work at the end, that did not work. -
I tried importing
zone.js
andzone.js/testing
in many places, but wasn't able to find any place that would fix this issue.
Please provide a link to a minimal reproduction of the bug
No response
Please provide the exception or error you saw
sd wan edit create form › edit connection › should call Update on submit if shouldUpdate is true
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Please provide the environment you discovered this bug in (run ng version
)
Angular CLI: 17.3.4
Node: 20.11.1
Package Manager: npm 10.5.1
OS: win32 x64
Angular: 17.3.4
... animations, cdk, cli, common, compiler, compiler-cli, core
... forms, language-service, localize, platform-browser
... platform-browser-dynamic, router
Package Version
---------------------------------------------------------
@angular-devkit/architect 0.1703.2
@angular-devkit/build-angular 17.3.4
@angular-devkit/core 17.3.4
@angular-devkit/schematics 17.3.4
@schematics/angular 17.3.4
ng-packagr 17.3.0
rxjs 7.8.1
typescript 5.4.3
zone.js 0.14.4
Anything else?
- Here is a gif of what the bug looks like in action (Note that it starts in the expect statement, I continue and hit the breakpoint in the fakeAsync cleanup, then after cleanup happens and the fakeAsyncZone is reset, I hit the breakpoint in the service that was meant to be called before the expect statement):
So the issue was that out tsconfig we were using for tests had "module": "ES2022"
in the compilerOptions
. I had assumed that the more recent version would work for Zone.js, but that was not the case. I swapped to CommonJS
and also added "target": "ES2016"
(I realize that it notes ES2015
here, but ES2016
still seemed to work (even though ES2022
didn't for some reason)).
Old:
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "ES2022",
"allowJs": true,
"types": ["jest", "node"]
},
New:
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "CommonJS",
"target": "ES2015",
"allowJs": true,
"types": ["jest", "node"]
},
If you run into this issue, just make sure that wherever you are configuring compilerOptions
for your tests, that you do not use ES2022
, downgrade it to ES2015
or ES2016