测试 - 无法解析 (ClassName) 的所有参数

Testing - Can't resolve all parameters for (ClassName)

上下文

我创建了一个 ApiService class 来处理我们的自定义 API 查询,同时使用我们自己的序列化程序和其他功能。

ApiService 的构造函数签名是:

constructor(metaManager: MetaManager, connector: ApiConnectorService, eventDispatcher: EventDispatcher);

问题

当我测试 ApiService 时,我在 beforeEach 中进行了初始化:

beforeEach(async(() => {
    TestBed.configureTestingModule({
        imports  : [
            HttpModule
        ],
        providers: [
            ApiConnectorService,
            ApiService,
            MetaManager,
            EventDispatcher,
            OFF_LOGGER_PROVIDERS
        ]
    });
}));

而且效果很好。

然后我添加了我的第二个规格文件,它是用于 ApiConnectorServicebeforeEach:

beforeEach(async(() => {
    TestBed.configureTestingModule({
        imports  : [HttpModule],
        providers: [
            ApiConnectorService,
            OFF_LOGGER_PROVIDERS,
            AuthenticationManager,
            EventDispatcher
        ]
    });
}));

并且所有测试都失败并出现此错误:

Error: Can't resolve all parameters for ApiService: (MetaManager, ?, EventDispatcher).

为什么我会出现这个错误?我的两个文件之间的上下文似乎有冲突,我不知道为什么以及如何解决这个问题。

这是因为 Http 服务在测试环境中无法从 HttpModule 解析。它取决于平台浏览器。在测试期间,您甚至不应该尝试进行 XHR 调用。

因此,Angular 提供了 MockBackendHttp 服务使用。我们使用这个模拟后端来订阅我们测试中的连接,并且我们可以在建立每个连接时模拟响应。

这是一个简短的完整示例,您可以使用

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 并在那里搜索它。由于我遇到了同样错误的另一个问题,我将在此处添加这两个解决方案。

  1. 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
  1. 我的问题的解决方案:

由于 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 中,改为注入 Injectorinjector.get(Service)