Angular ngx-translate Karma test fails with TypeError: this.missingTranslationHandler.handle is not a function

Viewed 981

By following this tutorial (https://indepth.dev/implementing-multi-language-angular-applications-rendered-on-server/) I've put in place a simple language translation system that works well. But when I launch my tests I have lot of errors I've then upgraded my angular from 8 to 10 that resolve some errors but this one is still remaning and I don't now how to fix it : TypeError: this.missingTranslationHandler.handle is not a function

Sometimes I have also a this.currentLoader.getTranslation is not a function error don't now why.

package.json

"dependencies": {
    "@angular/animations": "~10.0.7",
    "@angular/common": "~10.0.7",
    "@angular/compiler": "~10.0.7",
    "@angular/core": "~10.0.7",
    "@angular/forms": "~10.0.7",
    "@angular/platform-browser": "~10.0.7",
    "@angular/platform-browser-dynamic": "~10.0.7",
    "@angular/router": "~10.0.7",
    "@ngx-translate/core": "^13.0.0",
    "@ngx-translate/http-loader": "^6.0.0",
    "rxjs": "~6.6.2",
    "tslib": "^2.0.0",
    "zone.js": "~0.10.3"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.1000.5",
    "@angular/cli": "~10.0.5",
    "@angular/compiler-cli": "~10.0.7",
    "@angular/language-service": "~10.0.7",
    "@types/node": "^12.11.1",
    "@types/jasmine": "~3.3.8",
    "@types/jasminewd2": "~2.0.3",
    "codelyzer": "^6.0.0",
    "jasmine-core": "~3.5.0",
    "jasmine-spec-reporter": "~5.0.0",
    "karma": "~5.0.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage-istanbul-reporter": "~3.0.2",
    "karma-jasmine": "~3.3.0",
    "karma-jasmine-html-reporter": "^1.5.0",
    "protractor": "~7.0.0",
    "ts-node": "~7.0.0",
    "tslint": "~6.1.0",
    "typescript": "~3.9.7"
  }

i18n/i18n.module.ts

@NgModule({
  imports: [
    HttpClientModule,
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: translateLoaderFactory,
        deps: [HttpClient]
      }
    }),
  ],
  exports: [TranslateModule]
})
export class I18nModule {
  constructor(translate: TranslateService) {
    translate.addLangs(['en', 'fr']);
    const browserLang = translate.getBrowserLang();
    translate.use(browserLang.match(/en|fr/) ? browserLang : 'en');
  }
}

export function translateLoaderFactory(httpClient: HttpClient) {
  return new TranslateHttpLoader(httpClient);
}

app.module.ts

@NgModule({
  declarations: [
    AppComponent,
    NavigationComponent,
    HomeComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    I18nModule,
    TranslateModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

home/home.componenet.ts

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {

  public currentLang: string;
  text = '';

  constructor(private changeDetectorRef: ChangeDetectorRef, public translate: TranslateService) { }

  ngOnInit() {
    this.currentLang = this.translate.currentLang;
    this.translate.onLangChange.subscribe((event: LangChangeEvent) => {
      this.currentLang = event.lang;
      this.changeDetectorRef.detectChanges();
      this.translateText();
    });
    console.log('text: ', this.text);
  }

  translateText() {
    this.translate.get('default').subscribe((data: any) => {
      this.text = data.compA;
      console.log('text: ', this.text);
    });
  }

}

home/home.componenet.html

<select #langSelect (change)="translate.use(langSelect.value)">
    <option *ngFor="let lang of translate.getLangs()" [value]="lang"
        [attr.selected]="lang === translate.currentLang ? '' : null">{{lang}}</option>
</select>

<p>{{'default.compA' | translate}}</p>

<p translate>default.compB</p>

<p>{{text}}</p>

home/home.component.spec.ts

describe('HomeComponent', () => {
  let component: HomeComponent;
  let fixture: ComponentFixture<HomeComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [HomeComponent],
      imports: [
        TranslateModule
      ],
      providers: [
        TranslateService,
        TranslateStore,
        TranslateLoader,
        TranslateCompiler,
        TranslateParser,
        MissingTranslationHandler,
        { provide: USE_DEFAULT_LANG, useValue: undefined },
        { provide: USE_STORE, useValue: undefined },
        { provide: USE_EXTEND, useValue: undefined },
        { provide: DEFAULT_LANGUAGE, useValue: undefined }
      ]
    })
      .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(HomeComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

assets/i18n/en.json

{
    "default": {
        "compA": "Component A works",
        "compB": "Component B works"
    }
}

assets/i18n/fr.json

{
    "default": {
        "compA": "Le Composant A fonctionne",
        "compB": "Le Composant B fonctionne"
    }
}
1 Answers

If you put TranslationService in the providers first or if the TranslationModule in the imports is not well initalized then Karma tell you to add all this providers that you don't need.

What I've done is importing directly the I18nModule instead of the TranslationModule and kept only the TranslationService in the providers.

home/home.component.spec.ts

beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [HomeComponent],
      imports: [
        I18nModule
      ],
      providers: [
        TranslateService
      ]
    })
      .compileComponents();
  }));
Related