Jest-preset-angular: fakeAsync doesn't work

Created on 4 Mar 2019  路  10Comments  路  Source: thymikee/jest-preset-angular

Hi, I came across https://github.com/thymikee/jest-preset-angular/issues/65 and I'm currently facing the exact same issue with "zone.js": "0.8.29" and "jest": "24.1.0", "jest-preset-angular": "6.0.2". Any idea how to solve that?

My test is broken and then it ends up with

Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Error: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.

Most helpful comment

I don't understand either to be honest... If anyone understand why please share :smile_cat:

All 10 comments

Hi, would you please provide an example when it doesn't work ?

@ahnpnl just made a really simple repro here https://github.com/maxime1992/repro-jest-bug-fake-async

git clone [email protected]:maxime1992/repro-jest-bug-fake-async.git
cd repro-jest-bug-fake-async
yarn
yarn run test:watch

If anyone has any idea, I'd be glad to hear from you :smiley_cat:!

This is really a blocker to use Jest at all

It works for me, if I create the component that uses setTimeout in the constructor from inside the fakeAsync as well.

According to Angular docs (which itself points to a passage in the setup appendix), when using fakeAsync, zone.js/dist/zone-testing has to be imported in a setup file.

zone.js/dist/zone-testing is currently not usable with jest, as it patches jasmine.QueueRunner, which is obviously not available in jest.

Either we have to dive deeper into this and create some zone-testing on our own to patch setTimeout, or you have to call setTimeout inside fakeAsync only.

Please note that the example test repo https://github.com/maxime1992/repro-jest-bug-fake-async contains some errors:

  • constructor takes 10 seconds, but test only ticks 5 seconds
  • for the second test a new TestBed is generated, but the component is reused from the first test
  • If only the second test is run, app and fixture get never assigned

Hi @wtho

Thanks for pointing out the errors, I've fixed them.

It works for me, if I create the component that uses setTimeout in the constructor from inside the fakeAsync as well

How can that work? I've tried it and yes it works. But why?

According to what you said, it shouldn't, should it?

zone.js/dist/zone-testing is currently not usable with jest, as it patches jasmine.QueueRunner, which is obviously not available in jest.

I have been able to fix the test with the following:

describe('AppComponent', () => {
  let fixture: ComponentFixture<AppComponent>;
  let app: AppComponent;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [AppComponent],
    }).compileComponents();
  }));

  const setup = () => {
    fixture = TestBed.createComponent(AppComponent);
    app = fixture.debugElement.componentInstance;
  };

  it('should create the app', () => {
    setup();

    expect(app).toBeTruthy();
  });

  // following is not working
  it('should change the title after 5s', fakeAsync(() => {
    setup();

    tick(10000);
    expect(app.title).toBe('new title!');
  }));
});

A bit annoying to not use the beforeEach and call the setup function all the time but I could live with that. I just wonder if that will work in all the cases, observables using delay for exemple etc.

A bit annoying to not use the beforeEach and call the setup function all the time but I could live with that. I just wonder if that will work in all the cases, observables using delay for exemple etc.

I modified a bit your app.component.ts to add this:

ngOnInit() {
    this.testObservable$.pipe(
      delay(3000),
    ).subscribe(num => {
      console.log('print number', num);
    });
  }

and in app.component.spec.ts I added this:

it('should print number every 3s', fakeAsync(() => {
    setup();

    fixture.detectChanges();

    tick(3000);

    expect(console.log).toHaveBeenCalledWith('print number', 1);

    tick(3000);

    expect(console.log).toHaveBeenCalledWith('print number', 2);

    tick(3000);

    expect(console.log).toHaveBeenCalledWith('print number', 3);
  }));

It works perfectly.

Why does calling setup within the fakeAsync block
const setup = () => { fixture = TestBed.createComponent(TestWrapperComponent); component = fixture.componentInstance; };
rather than having this same code in a beforeEach() block work? I'm struggling to understand

I don't understand either to be honest... If anyone understand why please share :smile_cat:

@maxime1992 I was able to fix your test by simply defining fixture and app - ideally this would be done inside a beforeEach:

it('should change the title after 5s', fakeAsync(() => {
    fixture = TestBed.createComponent(AppComponent);
    app = fixture.debugElement.componentInstance;
    tick(5000);
    expect(app.title).toBe('new title!');
  }));

Also, you made a 10 second timeout in the app component, so obviously this would have to be less than or equal than 5000 for the test to pass.

Was this page helpful?
0 / 5 - 0 ratings