背景信息
我创建了一个ApiService
类来处理我们自定义的API查询,同时使用我们自己的序列化器和其他功能。
ApiService
的构造函数签名为:
constructor(metaManager: MetaManager, connector: ApiConnectorService, eventDispatcher: EventDispatcher);
-
MetaManager
是一个可注入的服务,用来处理api的元数据。 -
ApiConnectorService
是一个包裹Http
来添加自定义头和签名系统的服务。 -
EventDispatcher
基本上是Symfony的事件调度系统,在typescript中。
当我测试ApiService
时,我在beforeEach
中进行初始化:
beforeEach(async(() => {
TestBed.configureTestingModule({
imports : [
HttpModule
],
providers: [
ApiConnectorService,
ApiService,
MetaManager,
EventDispatcher,
OFF_LOGGER_PROVIDERS
]
});
}));
,它工作得很好
然后我添加第二个规范文件,这是ApiConnectorService
, beforeEach
:
beforeEach(async(() => {
TestBed.configureTestingModule({
imports : [HttpModule],
providers: [
ApiConnectorService,
OFF_LOGGER_PROVIDERS,
AuthenticationManager,
EventDispatcher
]
});
}));
所有的测试都失败了,出现了这个错误:
错误:无法解析ApiService: (MetaManager, ?, EventDispatcher)的所有参数。
- 如果我从加载的测试中删除
api-connector-service.spec.ts
(ApiConnectorService
的规范文件),ApiService
的测试将成功。 - 如果我从加载的测试中删除
api-service.spec.ts
(ApiService
的规范文件),ApiConnectorService
的测试将成功。
为什么会出现这个错误?这似乎是我的两个文件之间的上下文冲突,我不知道为什么和如何解决这个问题。
使用Jest?
如果有人到达这里,你正在使用Jest来测试你的Angular应用程序(希望我们是越来越多的少数),你会遇到这个错误,如果你没有发出装饰器("emitDecoratorMetadata":true
)。您需要更新您的tsconfig.spec.json
文件,使其看起来像:
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"emitDecoratorMetadata": true,
"outDir": "../../out-tsc/spec",
"types": [
"jest",
"node"
]
},
"files": [
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}
这是因为在测试环境中不能从HttpModule
中解析Http
服务。它依赖于平台浏览器。在测试期间,您甚至不应该尝试进行XHR调用。
MockBackend
供Http
服务使用。我们在测试中使用这个模拟后端来订阅连接,并且可以在建立每个连接时模拟响应。
这里有一个简短的完整的例子,你可以使用
import { Injectable } from '@angular/core';
import { async, inject, TestBed } from '@angular/core/testing';
import { MockBackend, MockConnection } from '@angular/http/testing';
import {
Http, HttpModule, XHRBackend, ResponseOptions,
Response, BaseRequestOptions
} from '@angular/http';
@Injectable()
class SomeService {
constructor(private _http: Http) {}
getSomething(url) {
return this._http.get(url).map(res => res.text());
}
}
describe('service: SomeService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{
provide: Http, useFactory: (backend, options) => {
return new Http(backend, options);
},
deps: [MockBackend, BaseRequestOptions]
},
MockBackend,
BaseRequestOptions,
SomeService
]
});
});
it('should get value',
async(inject([SomeService, MockBackend],
(service: SomeService, backend: MockBackend) => {
backend.connections.subscribe((conn: MockConnection) => {
const options: ResponseOptions = new ResponseOptions({body: 'hello'});
conn.mockRespond(new Response(options));
});
service.getSomething('http://dummy.com').subscribe(res => {
console.log('subcription called');
expect(res).toEqual('hello');
});
})));
});
选择的答案并没有真正解决问题,这实际上只是一个编写测试的建议,而是在评论中,您必须遵循链接并在那里搜索它。由于我遇到了同样错误的另一个问题,所以我将在这里添加两个解决方案。
- OP问题的解决方案:
如果你有一个桶(索引)。
export * from 'my.component' // using my.service via DI
export * from 'my.service'
那么你可能会得到像EXCEPTION: Can't resolve all parameters for MyComponent: (?)
这样的错误。
要解决这个问题,你必须在组件之前导出服务:
export * from 'my.service'
export * from 'my.component' // using my.service via DI
- 解决我的问题:
由于circular dependency
导致undefined
服务导入,可能会发生相同的错误。要检查,console.log(YourService)
导入后(在您的测试文件-问题发生的地方)。如果未定义,则可能已经创建了索引。Ts文件(桶)导出服务和使用它的文件(组件/效果/任何你正在测试的)-通过从导出服务的索引文件中导入服务(使其完整)。
找到该文件并直接从your.service.ts
文件而不是索引中导入所需的服务
(开玩笑和角)
在我的例子中,根本原因是循环依赖,而不是"import service from index"。的情况。ng build <project> --prod
没有发现"循环依赖"。
解决方案:
在服务/组件中,注入Injector
和injector.get(Service)
。
(开玩笑和角)
同样,当你使用一个外部模块,但你没有导入它,而是在你的服务中使用它时,可能会出现这个问题。
,
import { TestBed } from '@angular/core/testing';
import <ALL needed> from '@ngx-translate/core';
import { SettingsService } from '../../../app/core/services/settings/settings.service';
describe('SettingsService', () => {
let service: SettingsService;
beforeAll(() => {
TestBed.configureTestingModule({
providers: [
SettingsService,
// <All needed>
]
});
service = TestBed.inject<SettingsService>(SettingsService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
错误会让你无处可去…但是,如果你这样做:
import { TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { SettingsService } from '../../../app/core/services/settings/settings.service';
describe('SettingsService', () => {
let service: SettingsService;
beforeAll(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()], // <---
providers: [
SettingsService
]
});
service = TestBed.inject<SettingsService>(SettingsService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
问题消失。
[Jest和Angular]在我的例子中,我创建了一个虚拟组件类,它继承了一个基组件,这是我感兴趣的测试。问题是它被设置为使用默认构造函数,所以TestBed没有机会为我注入stubService。代码是这样的:
class DummyComponent extends MyBaseComponent {
constructor(localizationService: LocalizationService) {
super(localizationService) // this is needed constructor
}
...
let fixture: ComponentFixture<DummyComponent>
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [DummyComponent],
imports: [{ provide: MyService, useValue: MyStubService}],
})
})
fixture = TestBed.createComponent(DummyComponent) // <-- It was failing here
}
回头看似乎更明显,因为具体类必须定义构造函数才能获得服务。