I'm trying to test through Jasmine (version 3.99) an Angular (version 9) component that utilizes i18next for its translations. Note that the code for the component renders as desired when viewed through our application, however in the Jasmine test outlined below it does not (the full error message I received is listed near the bottom of the post). Also note that I'm not wanting to mockup the translations or any i18next functionality - i.e. I want my component to use/render the translations as normal. The component setup is as follows
constructor(@Inject(I18NEXT_SERVICE) private i18NextService: ITranslationService,
private myTranslationTextService: MyTranslationTextService
) {
...
}
public ngOnInit() {
const enTranslations = this.myTranslationTextService.getEnTranslations(); <--get translations in JSON
i18next
.init({
supportedLngs: ['en',...],
fallbackLng: 'en',
debug: true,
returnEmptyString: true,
ns: [
'translation',
],
resources: {
en: {
translation: enTranslations
},
... //other translations
},
interpolation: {
format: I18NextModule.interpolationFormat(defaultInterpolationFormat),
},
})
.then(() => {
this.getData(); //<--call i18NextService methods and gets core data for MyComponent's template
})
.catch(err => {
console.log(err);
});
}
getData() {
this.i18NextService.changeLanguage('en'); //<--calls method within i18NextService
...
}
My spec looks like the following:-
export function appInit(i18next: ITranslationService) {
//return () => i18next.init();
return () => {
let promise: Promise<I18NextLoadResult> = i18next.init({
lng: 'cimode',
});
return promise;
};
}
export function localeIdFactory(i18next: ITranslationService) {
return i18next.language;
}
export const I18N_PROVIDERS = [
{
provide: APP_INITIALIZER,
useFactory: appInit,
deps: [I18NEXT_SERVICE],
multi: true
},
{
provide: LOCALE_ID,
deps: [I18NEXT_SERVICE],
useFactory: localeIdFactory
},
];
describe('My component', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
let mock: MyMockDataService = new MyMockDataService();
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [MyComponent],
imports: [I18NextModule.forRoot()],
providers: [
//I18NEXT_SERVICE,
{ provide: I18NEXT_SERVICE },
//{ provide: I18NEXT_SERVICE, useValue: {} as ITranslationService },
//{ provide: I18NEXT_SERVICE, useValue: TestBed.get(I18NEXT_SERVICE) },
I18N_PROVIDERS,
MyTranslationTextService
],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
mockMyTranslationTextService = TestBed.inject(MyTranslationTextService) as jasmine.SpyObj<MyTranslationTextService>;
spyOn(mockMyTranslationTextService, 'getEnTranslations').and.returnValue(mock.getEnTranslations());
}));
beforeEach(() => {
fixture = TestBed.createComponent(ParentMStep2022Component);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should render correctly', () => {
expect(component).toBeTruthy();
})
});
MyMockDataService is simply:-
export class MyMockDataService {
getEnTranslations() {
return of(
[
{
"text1": "text1 EN phrase",
"text2": "text2 EN phrase",
...
}
]
);
}
}
However after trying a number of different options in my test - eg.
...I am getting:-
NullInjectorError: R3InjectorError(DynamicTestModule)[InjectionToken I18NEXT_SERVICE -> InjectionToken I18NEXT_SERVICE]:
NullInjectorError: No provider for InjectionToken I18NEXT_SERVICE!
To setup i18next I've followed https://github.com/Romanchuk/angular-i18next/blob/master/README.md - it refers to the test project at https://github.com/Romanchuk/angular-i18next/tree/master/libs/angular-i18next/src/tests/projectTests (note however the test project doesn't inject the i18NextService token in the constructor - injecting is the recommendation)
Can anyone shed some light ?
Thanks to @satanTime I had the idea of add the tick() code as follows to proactively run the already queued tasks
describe('My component', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
let mock: MyMockDataService = new MyMockDataService();
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [MyComponent],
imports: [I18NextModule.forRoot()],
providers: [
//I18NEXT_SERVICE,
//{ provide: I18NEXT_SERVICE },
//{ provide: I18NEXT_SERVICE, useValue: {} as ITranslationService },
//{ provide: I18NEXT_SERVICE, useValue: TestBed.get(I18NEXT_SERVICE) },
I18N_PROVIDERS,
MyTranslationTextService
],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
mockMyTranslationTextService = TestBed.inject(MyTranslationTextService) as jasmine.SpyObj<MyTranslationTextService>;
spyOn(mockMyTranslationTextService, 'getEnTranslations').and.returnValue(mock.getEnTranslations());
}));
beforeEach(() => {
fixture = TestBed.createComponent(ParentMStep2022Component);
component = fixture.componentInstance;
//fixture.detectChanges();
});
it('should render correctly', fakeAsync(() => {
fixture.detectChanges();
tick();
fixture.detectChanges();
tick();
fixture.detectChanges();
expect(component).toBeTruthy();
}));
});