ошибка с тестовым компонентом angular2 с тестовым стендом

пытаюсь изучить эту утилиту тестирования TestBed в angular-2 на простом примере и попал в свой первый блокировщик. поиск google или SO не дал ни одного подходящего примера,

Итак, у меня есть очень простой компонент header, как показано ниже:

import { Component } from '@angular/core';

@Component({
    selector: 'header',
    template: ''
})
export class HeaderComponent{
    public title: string;

    constructor(testparam: string){
        this.title = 'test';
    }
}

а затем получить его спецификацию, как показано ниже -

import { TestBed } from '@angular/core/testing';
import { HeaderComponent } from './header.component';

describe('HeaderComponent Test', () => {
    let component: HeaderComponent;

    beforeEach(() => {
        TestBed.configureTestingModule({
            declarations: [HeaderComponent]
        });

        const fixture = TestBed.createComponent(HeaderComponent);
        component = fixture.componentInstance;
    });

    it('should have the component defined', () => {
        expect(component).toBeDefined();
    });

    it('should initialize the title to test', () => {
        expect(component.title).toBe('test');
    });
});

запуск теста кармы кидает - Error: No provider for String! in karma.entry.js

karma.entry.js в основном просто устанавливает тестовую конфигурацию env для TestBed, а затем проходит через каждый тест в моей папке спецификаций, ниже мой karma.entry.js

require('core-js/es6');
require('core-js/es7/reflect');

require('es6-shim');
require('reflect-metadata');
require('zone.js/dist/zone');
require('zone.js/dist/long-stack-trace-zone');
require('zone.js/dist/proxy');
require('zone.js/dist/sync-test');
require('zone.js/dist/jasmine-patch');
require('zone.js/dist/async-test');
require('zone.js/dist/fake-async-test');
require('rxjs/Rx');

const browserTesting = require('@angular/platform-browser-dynamic/testing');
const coreTesting = require('@angular/core/testing');

coreTesting.TestBed.initTestEnvironment(
   browserTesting.BrowserDynamicTestingModule,
   browserTesting.platformBrowserDynamicTesting()
);

const context = require.context('../src', true, /\.spec\.ts$/);

context.keys().forEach(context);

Error.stackTraceLimit = Infinity;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000;

Если я удалю параметр из конструктора класса компонента, тесты пройдут, поэтому я думаю, что мне не хватает некоторой предварительной конфигурации, из-за которой TestBed.createComponent(HeaderComponent) неправильно компилирует конструктор компонента с параметром строкового типа.

Любая подсказка, что я могу упустить?


ОБНОВЛЕНИЕ:

если это кому-нибудь поможет - на основе ответа @mrkosima мой обновленный класс компонентов теперь выглядит так, как показано ниже, и все модульные тесты теперь проходят хорошо :)

import { Component,  OpaqueToken, Inject } from '@angular/core';

export let TITLE_TOKEN = new OpaqueToken('title token');

@Component({
    selector: 'header',
    template: '',
    providers: [{ provide: TITLE_TOKEN, useValue: 'test' }]
})
export class HeaderComponent{
    public title: string;

    constructor(@Inject(TITLE_TOKEN) titleParam: string){
        this.title = titleParam;
    }
}

person kkap    schedule 12.02.2017    source источник


Ответы (1)


Вы правы, что основная причина проблемы в аргументе конструктора.

Во время создания компонента Injector пытается разрешить все зависимости, перечисленные в конструкторе. Injector ищет зависимости по типу в провайдерах. Подробнее о внедрении зависимостей здесь: https://angular.io/docs/ts/latest/guide/dependency-injection.html

Это означает, что если компонент имеет constructor(authService: AuthService) { }, Injector ищет токен AuthService в провайдерах.

То же самое и в вашем случае - ваш компонент зависит от String. Но нет ни одного провайдера с токеном String.

На самом деле ошибочно указывать примитивный тип как зависимость.

Вместо этого следует использовать OpaqueToken

export let TITLE_TOKEN = new OpaqueToken('title token');

Настройте токен в поставщиках модулей

providers: [{ provide: TITLE_TOKEN, useValue: 'title value' }]

Чем ввести токен в компонент:

constructor(@Inject(TITLE_TOKEN) title: string) {
  this.title = title;
}

Это правильное использование инъекционного примитива.

Подробнее здесь: https://angular.io/docs/ts/latest/guide/dependency-injection.html#!#opaquetoken

PS: для тестирования вашего компонента необходимо добавить TITLE_TOKEN в модуль тестирования:

import {TITLE_TOKEN} from ...
TestBed.configureTestingModule({
      providers: [ { provide: TITLE_TOKEN, useValue: 'test' } ]
});

А затем создайте тестовый компонент и ожидайте title как 'test'.

person mrkosima    schedule 12.02.2017
comment
Итак, это угловая структура DI, которую TestBed использует для создания экземпляра класса компонента, из-за которой невозможно внедрить примитивные типы или интерфейсы прямо в конструктор компонента и нуждается в OpaqueToken оболочке. Раньше, когда я читал DI, я пропускал эту часть просто из-за лени :), и это было проблемой. Thnx @mrkosima, я думаю, что этот шаблон DI имеет смысл, потому что теперь с Typescript мы в основном будем использовать шаблоны OOPS и в основном будем передавать типы классов или угловые сервисы конструкторам компонентов, еще раз спасибо. - person kkap; 13.02.2017