非常感谢 Oisin,因为他的答案指引了我正确的方向。请把这个答案看作是对他回答的补充。
然而,我认为有两个方面需要进一步澄清:
我们没有处理竞态条件(race condition)。
竞态条件会意味着两个 beforeEach
实例同时运行,我们无法确定哪一个先结束。实际上,非异步的 beforeEach
先运行,而异步的则后运行。每一次都是如此。
当你有两个 beforeEach
且其中一个是异步的时候(包括使用 @angular/core/testing
提供的闪亮的 waitForAsync
封装器的情况),异步实例的执行会被推到执行队列的末尾。
我也觉得 Oisin 提出的解决方案:
[...] 把所有的设置放在一个同步的 beforeEach 中。
…太过于限制性了。它可以是异步的而不会有问题。
重要的部分是,TestBed.createComponent()
应该在 TestBed.configureTestingModule()
已经解决后运行。
就是这样。
为了让它更加清晰明了,这里有一个随机的例子:
import { TestBed, waitForAsync } from '@angular/core/testing';
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();
});
});
...应该被转化为:
import { TestBed, waitForAsync } from '@angular/core/testing';
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();
}));
});
第二个(同步的)
beforeEach
的代码被添加到了第一个(异步的)
beforeEach
中。就是这样。
compileComponents
返回一个 Promise。https://angular.io/guide/testing#calling-compilecomponents - Todd Sjolanderng test
(我希望许多人都在使用),则调用compileComponents()
是不必要的,因为ng
CLI已经为我们编译了组件。因此,不需要调用此方法,也不需要将async()
传递给beforeEach()
。因此,在整个测试套件中,我们可以获得同步调用而没有任何竞争条件。 - MladencompileComponents()
的目的是使测试可以在没有 CLI 的情况下运行,这在 CI 环境中非常常见。 - Mozgor