我有一个服务,该服务需要对另一个服务的依赖,而该服务又需要对两个抽象类的依赖。
(ThemeConfigService -> (SettingsService -> SettingsLoader, NavigationLoader))
到目前为止,由于无法找到通过抽象类公开的方法(不是函数异常),导致测试失败。
我不确定我将如何获得通过,事实证明,在线进行的各种搜索都没有帮助。
这是主题配置服务,我正在尝试为其充实测试“ theme-config.service.ts
”
@Injectable({
providedIn: 'root'
})
export class ThemeConfigService {
constructor(
private platform: Platform,
private router: Router,
private settings: SettingsService
) {
// code removed for brevity
}
}
这是正在测试的服务“ settings.service.ts
”
@Injectable()
export class SettingsService {
constructor(public settingsLoader: SettingsLoader,
public navigationLoader: NavigationLoader) { }
public settings(): Observable<any> {
return this.settingsLoader.retrieveSettings();
}
public navigation(): Observable<any> {
return this.navigationLoader.retrieveNavigation();
}
}
这里是SettingsLoader
类,NavigationLoader
看起来完全一样。从设计角度来看,它们必须是单独的类:
export abstract class SettingsLoader {
abstract retrieveSettings(): Observable<any>;
}
我的单元测试看起来像这样:
describe('ThemeConfigService', () => {
let service: ThemeConfigService;
let router: Router;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes([])
],
providers: [
Platform,
SettingsService,
SettingsLoader,
NavigationLoader
]
});
router = TestBed.inject(Router);
service = TestBed.inject(ThemeConfigService);
});
it('should be created', async(inject([Platform, Router, SettingsService, SettingsLoader, NavigationLoader],
(platform: Platform, router: Router, settings: SettingsService, settingsLoader: SettingsLoader, navigationLoader: NavigationLoader) => {
expect(service).toBeTruthy();
})));
});
业力返回的错误是:
TypeError: this.settingsLoader.retrieveSettings is not a function
which to me proves that it cannot resolve the abstract classes.
基于这个原因,我继续创建了以下内容:
export class SettingsFakeLoader extends SettingsLoader {
retrieveSettings(): Observable<any> {
return of({});
}
}
并尝试使用它们更改SettingsLoader
和NavigationLoader
类的注入,然后Karma做出响应:
NullInjectorError: R3InjectorError(DynamicTestModule)[ThemeConfigService -> SettingsService -> SettingsLoader -> SettingsLoader]:
NullInjectorError: No provider for SettingsLoader!
beforeEach
文件的修改后的theme-config.service.spec.ts
:
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
RouterModule,
RouterTestingModule.withRoutes([])
],
providers: [
Platform,
SettingsService,
SettingsFakeLoader,
NavigationFakeLoader
]
});
router = TestBed.inject(Router);
service = TestBed.inject(ThemeConfigService);
});
通常,我不会尝试测试我认为如此复杂的内容。也许我只是没有看到“解决方案”。
任何帮助将不胜感激,因为我将有类似的情况来解决此应用程序开发的后续问题。
答案 0 :(得分:0)
我走了实例化路线,而不是依赖于依赖注入。这可能不是一个可行的解决方案,但仍希望有更好方法的人来回答原始问题。
describe
文件的更新后的theme-config.service.spe.ts
:
describe('ThemeConfigService', () => {
let service: ThemeConfigService;
let sLoader: SettingsLoader;
let nLoader: NavigationLoader;
let sService: SettingsService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes([])
],
providers: [
ThemeConfigService,
SettingsService,
SettingsLoader,
NavigationLoader
]
});
let platform = TestBed.inject(Platform);
let router = TestBed.inject(Router);
sLoader = new SettingsFakeLoader();
nLoader = new NavigationFakeLoader();
sService = new SettingsService(sLoader, nLoader);
service = new ThemeConfigService(platform, router, sService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
答案 1 :(得分:0)
在Angular testing documentation中有一个很好的例子说明了您要做的事情:
beforeEach(() => {
// stub UserService for test purposes
userServiceStub = {
isLoggedIn: true,
user: { name: 'Test User' },
};
TestBed.configureTestingModule({
declarations: [ WelcomeComponent ],
providers: [ { provide: UserService, useValue: userServiceStub } ],
// ^^^^^^ Note use of `useValue` ^^^^^^
});
fixture = TestBed.createComponent(WelcomeComponent);
comp = fixture.componentInstance;
// UserService from the root injector
userService = TestBed.inject(UserService);
// get the "welcome" element by CSS selector (e.g., by class name)
el = fixture.nativeElement.querySelector('.welcome');
});
您可以创建实现服务的公共API的任何类的实例,然后在配置测试模块时为该实例提供useValue
。调用TestBed.inject(UserService)
会将值传递给依赖服务的构造函数。
在您的情况下,您可以使用jasmine.createSpyObj<SettingsLoader>("SettingsLoader", ["retrieveSettings"])
创建一个实现SettingsLoader
API的实例,然后将其作为providers
传递给useValue
。现在,TestBed.inject(ThemeConfigService)
将调用实际的SettingsService
构造函数,并向其传递您创建的间谍对象,然后调用ThemeConfigService
构造函数,并向其传递生成的SettingsService
实例。
切换到此模型而不是回答中的模型可能是一个好主意,因为如果将来您例如更新Platform
使其依赖于SettingsService
,您会突然发现自己的部分代码正在使用DI({{1}和platform
是使用{{ 1}}),但一部分不是(您是router
和inject(...)
自己)。最好预先正确配置DI,并允许注入器为您创建所有实例。