使用实用程序 class 和静态方法对组件进行单元测试
unit tests for component using utility class with static methods
我有组件使用 class LocaleService
和此服务的静态方法 instant()
。 LocaleService
未注入组件。在测试组件时,我对 LocaleService
内部结构不感兴趣,也不想测试它。因此在 beforeEach
我添加了一个间谍
const localeServiceInstantSpy = spyOn(LocaleService, 'instant');
localeServiceInstantSpy.and.callFake(msg => msg);
这很好用。现在我需要将这个间谍(和其他)移动到 LocaleService
的存根,并在这个测试中使用它,并使用 LocaleService
测试其他组件 - 它们有很多。实现这一目标的最正确方法是什么?如何创建可重用的 LocaleServiceStub?
\app\utils\locale.service.ts
export class LocaleService {
public static lang: string;
private static messages = {
'user.add': {
en: 'Add Local User Account',
de: 'Add Local User Account DE'
},
'user.edit': {
en: 'Edit Local User Account',
de: 'Edit Local User Account DE'
}
};
public static instant(key: string) {
return this.messages[key][this.lang];
}
}
在 class 测试中的用法 \app\settings\users\user-form.component.ts
import { LocaleService } from 'app/utils/locale.service';
...
getDialogHeader() {
return this.isNewUser ? LocaleService.instant('user.add') : LocaleService.instant('user.edit');
}
...
纯静态 classes 在 JavaScript 中有代码味道。如果 class 从未实例化,则没有必要。
这是 Angular DI 应该解决的情况之一。它应该被重构为一个服务而不是直接使用的class。
class LocaleService {
public lang: string;
private messages = {...};
public instant(key: string) {
return this.messages[key][this.lang];
}
}
...
providers: [LocaleService, ...]
...
然后就可以通过DI来mock了。为了被重用,可以将 mock 定义为提供者:
const LOCALE_SERVICE_MOCK = {
provide: LocaleService,
useFactory: () => ({
instant: jasmine.createSpy('instant').and.callFake(msg => msg)
})
};
并在试验台指定:
beforeEach(() => {
TestBed.configureTestingModule({ providers: [LOCALE_SERVICE_MOCK]});
});
或者用模块包裹:
beforeEach(() => {
TestBed.configureTestingModule({ imports: [LocaleServiceMockModule]});
});
在当前状态下,可以通过将可重用代码移至函数来使代码更干燥:
function mockLocaleService() {
const localeServiceInstantSpy = spyOn(LocaleService, 'instant');
localeServiceInstantSpy.and.callFake(msg => msg);
}
并在需要的地方使用它:
beforeEach(mockLocaleService);
我有组件使用 class LocaleService
和此服务的静态方法 instant()
。 LocaleService
未注入组件。在测试组件时,我对 LocaleService
内部结构不感兴趣,也不想测试它。因此在 beforeEach
我添加了一个间谍
const localeServiceInstantSpy = spyOn(LocaleService, 'instant');
localeServiceInstantSpy.and.callFake(msg => msg);
这很好用。现在我需要将这个间谍(和其他)移动到 LocaleService
的存根,并在这个测试中使用它,并使用 LocaleService
测试其他组件 - 它们有很多。实现这一目标的最正确方法是什么?如何创建可重用的 LocaleServiceStub?
\app\utils\locale.service.ts
export class LocaleService {
public static lang: string;
private static messages = {
'user.add': {
en: 'Add Local User Account',
de: 'Add Local User Account DE'
},
'user.edit': {
en: 'Edit Local User Account',
de: 'Edit Local User Account DE'
}
};
public static instant(key: string) {
return this.messages[key][this.lang];
}
}
在 class 测试中的用法 \app\settings\users\user-form.component.ts
import { LocaleService } from 'app/utils/locale.service';
...
getDialogHeader() {
return this.isNewUser ? LocaleService.instant('user.add') : LocaleService.instant('user.edit');
}
...
纯静态 classes 在 JavaScript 中有代码味道。如果 class 从未实例化,则没有必要。
这是 Angular DI 应该解决的情况之一。它应该被重构为一个服务而不是直接使用的class。
class LocaleService {
public lang: string;
private messages = {...};
public instant(key: string) {
return this.messages[key][this.lang];
}
}
...
providers: [LocaleService, ...]
...
然后就可以通过DI来mock了。为了被重用,可以将 mock 定义为提供者:
const LOCALE_SERVICE_MOCK = {
provide: LocaleService,
useFactory: () => ({
instant: jasmine.createSpy('instant').and.callFake(msg => msg)
})
};
并在试验台指定:
beforeEach(() => {
TestBed.configureTestingModule({ providers: [LOCALE_SERVICE_MOCK]});
});
或者用模块包裹:
beforeEach(() => {
TestBed.configureTestingModule({ imports: [LocaleServiceMockModule]});
});
在当前状态下,可以通过将可重用代码移至函数来使代码更干燥:
function mockLocaleService() {
const localeServiceInstantSpy = spyOn(LocaleService, 'instant');
localeServiceInstantSpy.and.callFake(msg => msg);
}
并在需要的地方使用它:
beforeEach(mockLocaleService);