测试 - 无法解析 (ClassName) 的所有参数
Testing - Can't resolve all parameters for (ClassName)
上下文
我创建了一个 ApiService
class 来处理我们的自定义 API 查询,同时使用我们自己的序列化程序和其他功能。
ApiService
的构造函数签名是:
constructor(metaManager: MetaManager, connector: ApiConnectorService, eventDispatcher: EventDispatcher);
MetaManager
是一种可注入服务,可处理 api 的元数据。
ApiConnectorService
是一项包装 Http
以添加我们的自定义 headers 和签名系统的服务。
EventDispatcher
基本上是 Symfony 的事件调度系统,在打字稿中。
问题
当我测试 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
]
});
}));
并且所有测试都失败并出现此错误:
Error: Can't resolve all parameters for ApiService: (MetaManager, ?, EventDispatcher).
- 如果我从加载的测试中删除
api-connector-service.spec.ts
(ApiConnectorService
的规范文件),ApiService
的测试将会成功。
- 如果我从加载的测试中删除
api-service.spec.ts
(ApiService
的规范文件),ApiConnectorService
的测试将会成功。
为什么我会出现这个错误?我的两个文件之间的上下文似乎有冲突,我不知道为什么以及如何解决这个问题。
这是因为 Http
服务在测试环境中无法从 HttpModule
解析。它取决于平台浏览器。在测试期间,您甚至不应该尝试进行 XHR 调用。
因此,Angular 提供了 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');
});
})));
});
这个问题在选择的答案中并没有真正解决,这实际上只是一个编写测试的建议,而是在评论中,你必须按照 link 并在那里搜索它。由于我遇到了同样错误的另一个问题,我将在此处添加这两个解决方案。
- OP 问题的解决方案:
如果您有这样的桶(index.ts 或多导出文件):
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)
导入后(在您的测试文件中 - 问题发生的地方)。如果它未定义,您可能已经制作了一个 index.ts 文件(桶)导出服务和使用它的文件(component/effect/whatever 您正在测试) - 通过从索引文件导入服务导出(使其成为一个完整的圆)。
找到该文件并直接从 your.service.ts
文件而不是索引导入您需要的服务。
使用 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"
]
}
[JEST 和 ANGULAR]
另外,当您使用外部模块时,您没有导入它,而是在您的服务中使用它,也可能会出现此问题。
例如:
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] 在我的例子中,我创建了一个虚拟组件 class,它继承了我感兴趣的测试基础组件。问题是它被设置为使用默认构造函数,因此 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
}
回头看似乎更明显,因为具体的 class 必须定义构造函数才能获得服务。我只是认为那将是默认构造函数。
[JEST 和 ANGULAR]
在我的例子中,根本原因是循环依赖,而不是“从索引导入服务”的情况。并且ng build <project> --prod
没有找到“循环依赖”。
解决方案:
在 service/component 中,改为注入 Injector
和 injector.get(Service)
。
上下文
我创建了一个 ApiService
class 来处理我们的自定义 API 查询,同时使用我们自己的序列化程序和其他功能。
ApiService
的构造函数签名是:
constructor(metaManager: MetaManager, connector: ApiConnectorService, eventDispatcher: EventDispatcher);
MetaManager
是一种可注入服务,可处理 api 的元数据。ApiConnectorService
是一项包装Http
以添加我们的自定义 headers 和签名系统的服务。EventDispatcher
基本上是 Symfony 的事件调度系统,在打字稿中。
问题
当我测试 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
]
});
}));
并且所有测试都失败并出现此错误:
Error: Can't resolve all parameters for ApiService: (MetaManager, ?, EventDispatcher).
- 如果我从加载的测试中删除
api-connector-service.spec.ts
(ApiConnectorService
的规范文件),ApiService
的测试将会成功。 - 如果我从加载的测试中删除
api-service.spec.ts
(ApiService
的规范文件),ApiConnectorService
的测试将会成功。
为什么我会出现这个错误?我的两个文件之间的上下文似乎有冲突,我不知道为什么以及如何解决这个问题。
这是因为 Http
服务在测试环境中无法从 HttpModule
解析。它取决于平台浏览器。在测试期间,您甚至不应该尝试进行 XHR 调用。
因此,Angular 提供了 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');
});
})));
});
这个问题在选择的答案中并没有真正解决,这实际上只是一个编写测试的建议,而是在评论中,你必须按照 link 并在那里搜索它。由于我遇到了同样错误的另一个问题,我将在此处添加这两个解决方案。
- OP 问题的解决方案:
如果您有这样的桶(index.ts 或多导出文件):
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)
导入后(在您的测试文件中 - 问题发生的地方)。如果它未定义,您可能已经制作了一个 index.ts 文件(桶)导出服务和使用它的文件(component/effect/whatever 您正在测试) - 通过从索引文件导入服务导出(使其成为一个完整的圆)。
找到该文件并直接从 your.service.ts
文件而不是索引导入您需要的服务。
使用 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"
]
}
[JEST 和 ANGULAR]
另外,当您使用外部模块时,您没有导入它,而是在您的服务中使用它,也可能会出现此问题。
例如:
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] 在我的例子中,我创建了一个虚拟组件 class,它继承了我感兴趣的测试基础组件。问题是它被设置为使用默认构造函数,因此 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
}
回头看似乎更明显,因为具体的 class 必须定义构造函数才能获得服务。我只是认为那将是默认构造函数。
[JEST 和 ANGULAR]
在我的例子中,根本原因是循环依赖,而不是“从索引导入服务”的情况。并且ng build <project> --prod
没有找到“循环依赖”。
解决方案:
在 service/component 中,改为注入 Injector
和 injector.get(Service)
。