Тестирование - не удается разрешить все параметры для (ClassName)

Context

Я создал класс ApiService, чтобы иметь возможность обрабатывать наши пользовательские запросы API, используя наш собственный сериализатор и другие функции.

Подпись конструктора ApiService:

constructor(metaManager: MetaManager, connector: ApiConnectorService, eventDispatcher: EventDispatcher);
  • MetaManager - это инъекционная служба, которая обрабатывает метаданные API.
  • ApiConnectorService - это служба, которая упаковывает Http, чтобы добавить наши собственные заголовки и систему подписи.
  • EventDispatcher - это, по сути, система диспетчера событий Symfony в машинописном тексте.

Problem

Когда я тестирую 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 пройдут успешно.

Почему у меня такая ошибка? Похоже, что контекст между двумя моими файлами находится в конфликте, и я не знаю, почему и как это исправить.


person Supamiu    schedule 13.09.2016    source источник


Ответы (6)


Это потому, что служба 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');
	});
  })));
});

person Paul Samsotha    schedule 14.09.2016
comment
Почему бы вам просто не использовать MockBackend, BaseRequestOptions вместо { provide: MockBackend, useClass: MockBackend }, { provide: BaseRequestOptions, useClass: BaseRequestOptions },. Разве эти два уже не предоставлены HttpModule (я почти уверен, что они есть). Также providers: [ { provide: Http, useFactory: (backend, options) => { return new Http(backend, options); }, deps: [MockBackend, BaseRequestOptions] }, можно сократить до {provide: XHRBackend: useExisting: MockBackend} - person Günter Zöchbauer; 14.09.2016
comment
Итак, providers: [{provide: XHRBackend: useExisting: MockBackend}, SomeService] должен дать вам тот же результат. - person Günter Zöchbauer; 14.09.2016
comment
@ GünterZöchbauer Да, ты прав. Раньше я использовал XHRBackend в качестве токена, но забыл, что вводил тип MockBackend. Поэтому я изменил его на токен MockBackend. В то время я не думал о стиле, это было просто быстрое исправление в то время, когда я тестировал. - person Paul Samsotha; 14.09.2016
comment
Насколько я помню, документы на Angular.io также показывают этот долгий путь. Ваш ответ в порядке. Просто хотел указать на это. Я видел много примеров, когда люди используют слишком сложную конфигурацию, а затем жалуются, что DI громоздок ;-) - person Günter Zöchbauer; 14.09.2016
comment
Так почему же параметр ApiConnectorService не разрешен? а почему только когда запускаю оба теста? - person Supamiu; 14.09.2016
comment
Может быть, потому, что вы пытаетесь внедрить службу apiservice, а не службу соединителя, и ошибка сообщает вам, чего не хватает в службе, которую вы пытаетесь внедрить. - person Paul Samsotha; 14.09.2016
comment
Посмотрите на пример;) это то, что я ввожу. Я изменил HttpModule на MockBackend и попробовал этот способ, но с той же ошибкой. - person Supamiu; 14.09.2016
comment
Можете выложить полный пример. Достаточно, чтобы воспроизвести проблему. Что-то вроде того, что я сделал выше. - person Paul Samsotha; 14.09.2016
comment
Я попытался создать полный пример, используя шаблон быстрого запуска, похоже, не могу. Проблема должна быть в моем коде, но его слишком сложно вставить сюда. Поскольку все поставщики хороши, и я сейчас предоставляю хороший Http (с MockBackend), я не вижу, откуда это может взяться ... - person Supamiu; 14.09.2016
comment
Я бы просто добавил урезанные версии классов только с конструктором и единственным методом для тестирования. Проблема заключается в инъекции, которую можно воспроизвести без чего-либо еще в классах. Если вы заставите его работать с урезанными версиями, начинайте добавлять что-то, пока оно не сломается. Тогда вы знаете, в чем проблема. - person Paul Samsotha; 14.09.2016
comment
Я только что наткнулся на этот github.com/angular/angular.io/issues / и думал о тебе. Снимок в темноте, но может ли это быть проблемой? - person Paul Samsotha; 14.09.2016
comment
@peeskillet, это именно та проблема, с которой я столкнулся, я просто пришел, чтобы сказать это, и увидел ваш комментарий ...: D - person Supamiu; 14.09.2016
comment
Мой конструктор службы выглядит как constructor(private http: Http, baseName: string) {, так что где мне нужно изменить в приведенном выше коде? - person Avinash Raj; 02.01.2017

Используете Jest?

В случае, если кто-то попадет сюда, и вы используете Jest для тестирования своего приложения Angular (надеюсь, мы - растущее меньшинство), вы столкнетесь с этой ошибкой, если не испускаете декораторы. Вам нужно будет обновить ваш tsconfig.spec.json файл, чтобы он выглядел так:

{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "outDir": "../../out-tsc/spec",
    "types": [
      "jest",
      "node"
    ]
  },
  "files": [
  ],
  "include": [
    "**/*.spec.ts",
    "**/*.d.ts"
  ]
}
person Mike Dalrymple    schedule 01.02.2020
comment
Спасибо. В сообщении об ошибке нет ничего, что помогло бы в этом разобраться. Ключ emitDecoratorMetadata: true - person Stephen; 11.07.2020

На самом деле проблема не была решена в выбранном ответе, который на самом деле является просто рекомендацией для написания тестов, а скорее в комментариях, и вы должны перейти по ссылке и найти ее там. Поскольку у меня была еще одна проблема с той же ошибкой, я добавлю здесь оба решения.

  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 файла, а не из индекса.

person Kesarion    schedule 03.07.2017

[JEST и ANGULAR]

Кроме того, проблема может возникнуть, если вы используете внешний модуль и не импортируете его, а используете в своей службе.

Ex:

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

});

Проблема исчезает.

person Michal S.    schedule 28.10.2020

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

Оглядываясь назад, это кажется более очевидным, потому что конкретный класс должен будет определить конструктор для получения услуги. Я просто подумал, что это будет конструктор по умолчанию.

person Felipe Centeno    schedule 04.02.2021

[JEST и ANGULAR]

В моем случае основной причиной является циклическая зависимость, а не служба импорта из индекса. И ng build <project> --prod не обнаружил круговой зависимости.

Решение:

В сервисе / компоненте вместо этого вводятся Injector и injector.get(Service).

person leo    schedule 28.05.2021