I'm extremely grateful to Oisin, as his answer pointed me into the right direction. Please consider this answer as a complement to his.
There are, however, two aspects which, IMHO, need clarification:
We're not dealing with a race condition.
A race condition would mean the two instances of beforeEach
run in parallel and we cannot determine which one finishes first. In reality, the non-async beforeEach
runs first and the async
one runs second.
Every single time.
When you have two beforeEach
s and one of them is async
(including the ones using the shiny waitForAsync
wrapper provided by @angular/core/testing
), the async instance's execution gets pushed at the end of the execution queue.
I also find Oisin's proposed solution:
[...] put all the setup into one, synchronous beforeEach.
... too limiting. It doesn't have to be synchronous. It can be asynchronous without a problem.
The important bit is that, TestBed.createComponent()
should run after TestBed.configureTestingModule()
has resolved.
That's all there is to it.
To make it crystal clear, this random example:
import { TestBed, waitForAsync } from '@angular/core/testing';
// more imports...
describe('SomeComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [SomeComponent],
imports: [SharedModule, RouterTestingModule, HttpClientTestingModule],
providers: [{
provide: SomeService, useValue: {
someObservableMethod$: () => of()
} as Partial<SomeService>
}]
})
.overrideModule(MatIconModule, MatIconModuleMock)
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
/* tests here */
});
... should be turned into:
import { TestBed, waitForAsync } from '@angular/core/testing';
// more imports...
describe('SomeComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [SomeComponent],
imports: [SharedModule, RouterTestingModule, HttpClientTestingModule],
providers: [{
provide: SomeService, useValue: {
someObservableMethod$: () => of()
} as Partial<SomeService>
}]
})
.overrideModule(MatIconModule, MatIconModuleMock)
.compileComponents();
fixture = TestBed.createComponent(SomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}));
/* tests here */
});
The code in the second (synchronous) beforeEach
was appended to the first (asynchronous) beforeEach
. That's it.