Reputation: 23
I try to build an Angular component which depends on an Injector providing an InjectorToken:
import { Component, Injector, InjectionToken } from '@angular/core';
const TOKEN = new InjectionToken<string>('token');
@Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.scss']
})
export class TestComponent {
constructor(injector: Injector) {
injector.get(TOKEN);
}
}
However, I can not get the test running for this component and get following error:
NullInjectorError: StaticInjectorError(DynamicTestModule)[InjectionToken token]:
StaticInjectorError(Platform: core)[InjectionToken token]:
NullInjectorError: No provider for InjectionToken token!
The following approach to provide the Injector in the TestBed didn't work:
TestBed.configureTestingModule({
declarations: [ TestComponent ],
providers: [
{ provide: Injector, useValue: Injector.create({providers: [
{
provide: new InjectionToken<string>('token'),
useValue: '',
deps: []
}
]}) }
]
})
How can I setup the test environment correctly?
Upvotes: 1
Views: 3348
Reputation: 2046
I was also unable to set up the spec the way you did in your example, but there is a simpler way. I think there are two sources of your problem. The first is that you are building a new InjectorToken when testing. The important thing is illustrated here:
const inj1 = new InjectorToken<string>('token');
const inj2 = new InjectorToken<string>('token');
console.log(inj1 === inj2); // logs false
The log will be false
, even though they use the same string, they are different objects. I suggest building an injection token and exporting it as a constant like so:
export const TOKEN = new InjectionToken<string>('token');
Then import TOKEN
everywhere you need to inject whatever you are injecting.
The second issue is that you are mocking the injector with a new injector. That is a lot of wiring. I was not able to get this working at all. But you can simplify your spec like so:
import { TOKEN } from './token.const';
describe('token', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ TestComponent ],
providers: [
{ provide: TOKEN, useValue: '' }
]
});
});
});
By doing this, you use the existing injector, but you mock what is injected. This is typically the goal.
You may also want to consider using the inject decorator in your component like so:
import { Component, Inject, InjectionToken } from '@angular/core';
import { TOKEN } from './token.const';
@Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.scss']
})
export class TestComponent {
constructor(@Inject(TOKEN) value: any) {}
}
This is a simpler way of getting the injected value and is cleaner. You'd write the spec in the same exact way.
Upvotes: 2
Reputation: 1783
Just provide the token which you want to inject. The injector is created for you automatically. You don't have to create it for yourself.
// app.component.ts
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'playground-app';
config: string;
constructor(private injector: Injector) {
this.config = injector.get('CONFIG_TOKEN');
}
}
// app.component.spec.ts
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
providers: [
{ provide: 'CONFIG_TOKEN', useValue: 'Test' }
]
}).compileComponents();
}));
it(`should inject the config`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.config).toEqual('Test');
});
});
Upvotes: 0