所有代码都必须被单元测试覆盖。
每次测试测试一件事(在一个 it 里实现)。
单元测试将被测试的代码与其依赖项隔离:模拟所有的依赖项。
单元测试需要相互独立:我们应该能够以任何顺序从文件运行测试,并且不会改变结果。
在适用时涵盖快乐路径、错误、边缘情况和 UI。
UI Components
Mock Everything
与任何其他代码段一样,我们希望单独测试 UI 组件。 我们不希望测试结果受到我们正在测试的类之外的代码的影响。
虽然模拟服务之类的依赖关系更为明显,但很容易忘记模拟从测试组件的模板中调用的子组件。
要模拟子组件,您可以在规范文件中创建它的假副本。 伪造组件必须与您要伪造的子组件具有相同的选择器:
例子:
@Component({ template: '', selector: 'cx-some-component' }) class MockSomeComponent { @Input() someparam; }
Then, you declare it in the TestBed:
TestBed.configureTestingModule({ imports: [ ... ], declarations: [MockSomeComponent], providers: [ ... ], }).compileComponents();
NGRX and Tests That Use the Store
事实证明,模拟 NGRX 商店是一项相当大的挑战。 NGRX 存储是我们单元测试中模拟依赖项规则的例外。
要对从 store 读取的一段代码执行单元测试,请通过使用数据显式调用相关成功操作来填充 store 以设置测试。
这是我们调度 LoadUserAddressesSuccess 来设置测试数据的示例:
it('should be able to get user addresses', () => { const mockUserAddresses: Address[] = [{ id: 'address1' }, { id: 'address2' }]; store.dispatch(new UserActions.LoadUserAddressesSuccess(mockUserAddresses)); let addresses: Address[]; service .getAddresses() .subscribe(data => { addresses = data; }) .unsubscribe(); expect(addresses).toEqual([{ id: 'address1' }, { id: 'address2' }]); });
Avoiding Silently Failing Tests
最好的做法是删除订阅之外的断言。 这样,我们确保在测试完成之前执行断言。 为了断言 observable 的结果,我们在订阅中分配结果,但断言是在订阅之外用值完成的。
下列做法不推荐:
service .getAddresses() .subscribe(addresses => { expect(addresses).toEqual([{ id: 'address1' }, { id: 'address2' }]); }) .unsubscribe(); });