Unhandled promise rejection: Error: Injector has already been destroyed
Asked Answered
M

5

17

In this issue that was recently closed: https://github.com/angular/angular/issues/44186

A contributer (@jessicajaniuk) says

We believe this is resolved by destroyAfterEach: true. If you're seeing failures that require destroyAfterEach: false, it's likely you have scope leakage in your tests. If you continue to see the original issue, please open a new issue for it.

I was wondering if anyone can elaborate on or suggest strategies on how to identify "scope creep"

I'm also running into this after upgrading to Angular 13 with destroyAfterEach: true

Millian answered 7/6, 2022 at 8:27 Comment(0)
N
19

The answer of @user11883568 got me in the right direction but using fakeAsync didn't help in my case. What I did instead, was to opt-out from teardown on the unit-tests affected by this problem by adding the following as described here:

For the complete suite, on the existing TestBed.configureTestingModule, just add the line:

beforeEach(async () => {
    await TestBed.configureTestingModule({
        imports: [...],
        declarations: [...],
        providers: [...],
        teardown: {destroyAfterEach: false}   // <- add this line
    }).compileComponents();
});

Or, if the problem occurs only on some of the tests, you can opt-out a describe block with:

beforeAll(() => {
    // Deactivate teardown for these tests because of a problem with
    // the primeNg dialog.
    TestBed.resetTestEnvironment();
    TestBed.initTestEnvironment(
        BrowserDynamicTestingModule,
        platformBrowserDynamicTesting(),
        {teardown: {destroyAfterEach: false}}
    );
});
Nicias answered 2/11, 2022 at 23:57 Comment(4)
If your usign setTimeout() in function, just add tick() greater then setTimeout in test.Hysteria
@Hysteria I'm not using setTimeout() at all. My problem is caused by a third-party component (a primeNG dialog).Nicias
I tried refactoring my test case but to no avail. After adding teardown: {destroyAfterEach: false} // <- add this line to the specific .spec file - the issue did not popup again. Still seems like a bandaid fix :(Overrun
@JonathanCardoz yes, it's definitely just a workaround and it'd be better to have a real fix for the actual problem (in our case the primeNG dialog), but at least for us, the affected tests are just a few out of 3000+ tests, so this is a LOT better than having to deactivate the teardown completely, which has a huge performance loss.Nicias
S
5

Using PrimeNG as component library I got this problem in tests for components using any kind of overlay or popup (e.g. dialog, calendar). Simply running these tests in the fakeAsync zone fixed it.

I guess it has something to do with pending animations, but my first try - using NoopAnimationsModule in the test setup - did not help.

Slavery answered 10/10, 2022 at 8:20 Comment(2)
Why was this voted down? It is a solution that worked perfectly in my special constellation. Never claimed it to be a general solution, but a hint ("look for external libs doing background tasks like animations").Slavery
In my case using fakeAsync didn't help. But I could confirm and narrow down the problem being related to prime-ng dialogs.Nicias
M
5

For my case this was a symptom of a hidden setTimeout within a service method called by the component under test. The destroyAfterEach solution may work but it ignores the root problem which is logic being executed after the test expects to be finished.

So there are 4 solutions:

  1. Try using destroyAfterEach as the other answers outline
  2. Use fakeAsync to wrap the test and likely add a flush to clear the task queue
  3. spyOn the method which contains the asynchronous logic
  4. Remove the setTimeout or rewrite the asynchronous logic (with promises or async/await or observables) and write your tests to wait for the logic to finish.

Depending on what you are trying to test only #4 truly executes the code which is causing the error from the OP.

Morrison answered 10/2, 2023 at 22:28 Comment(0)
S
3

Setting teardown: { destroyAfterEach: false } in test.ts file solved the problem for me.

Sluggish answered 22/12, 2022 at 11:9 Comment(1)
How is this different from accepted answer? And - this feels like the real problem was just swept under the rug.Voluptuous
M
1

I ran into this issue when testing a NGRX Observable.

The solution was to wrap the tests inside of .toSatisfyOnFlush():

expect(observable$).toSatisfyOnFlush(() => {
    expect(observable$).toBeObservable(hot(''));  // Error occurred when this was outside
    expect(mySpy).toHaveBeenCalled();
});
Mweru answered 8/11, 2023 at 16:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.