angular / angular

Deliver web apps with confidence 🚀

Home Page:https://angular.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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() and flushMicrotasks() 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 and zone.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): enter image description here

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