26

语境

我创建了一个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
        ]
    });
}));

它工作正常。

然后我添加我的第二个规范文件,即 for 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则测试将成功。

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

4

6 回答 6

24

使用玩笑?

万一有人来到这里并且您正在使用 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"
  ]
}
于 2020-02-01T23:29:38.573 回答
20

这是因为在测试环境中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');
	});
  })));
});

于 2016-09-14T06:19:48.027 回答
9

这个问题在选择的答案中并没有真正解决,这实际上只是对编写测试的建议,而是在评论中,你必须点击一个链接并在那里搜索它。由于我遇到了相同错误的另一个问题,因此我将在此处添加两种解决方案。

  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 文件(桶),导出服务和使用它的文件(组件/效果/您正在测试的任何内容) - 通过从导出两者的索引文件导入服务(使其完整循环)。

找到该文件并直接从your.service.ts文件而不是索引导入您需要的服务。

于 2017-07-03T10:55:21.190 回答
1

[开玩笑和角度]

此外,当您使用外部模块并且您不导入它但在您的服务上使用它时,可能会出现问题。

前任:

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();
  });

});

问题消失。

于 2020-10-28T13:37:12.977 回答
1

[开玩笑和角度]

就我而言,根本原因是循环依赖,而不是“从索引导入服务”的情况。并ng build <project> --prod没有找到“循环依赖”。

解决方案:

在服务/组件中,注入Injectorinjector.get(Service)代替。

于 2021-05-28T09:26:18.363 回答
0

[Jest and 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
}

回想起来似乎更明显,因为具体类必须定义构造函数才能获得服务。我只是认为这将是默认构造函数。

于 2021-02-04T17:43:25.000 回答