tiwarib
tiwarib

Reputation: 451

error with angular2 testing component with Testbed

trying to learn this testing utility TestBed in angular-2 with a simple example and have hit my first blocker. google or SO search didn't yield any matching example,

so, I have a very basic component header as below -

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

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

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

and then have its spec as below -

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

running the karma test is throwing - Error: No provider for String! in karma.entry.js

karma.entry.js is basically just setting the test env configuration for TestBed and then goes thru each test in my spec folder, below is my 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;

If I remove the parameter from the constructor of the component class, the tests pass, so I am thinking that I am missing some pre-configuration thats causing the TestBed.createComponent(HeaderComponent) not to properly compile the component's constructor with the string type parameter.

any clue what I might be missing?


UPDATE:

if it helps anyone - based on @mrkosima's answer, my updated component class now looks like below and the unit tests all pass good now :)

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

Upvotes: 7

Views: 4187

Answers (1)

mrkosima
mrkosima

Reputation: 507

You are right that the root cause of issue in the constructor's argument.

During component instantiation Injector trying to resolve all dependencies listed in constructor. Injector looks up dependencies by type in providers. More about DI here: https://angular.io/docs/ts/latest/guide/dependency-injection.html

That means if component has constructor(authService: AuthService) { }, the Injector looking for AuthService token in providers.

The same in your case - your component depends on String. But there is no any provider with String token.

Actually, it's a mistake to list primitive type as dependency.

Instead of this OpaqueToken should be used

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

Configure token in module providers

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

Than inject token in component:

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

That's the correct usage of injecting primitive.

More details here: https://angular.io/docs/ts/latest/guide/dependency-injection.html#!#opaquetoken

PS: to test your component the TITLE_TOKEN should be added to testing module:

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

And than create test component and expect title as 'test'.

Upvotes: 6

Related Questions