DEV Community

Roman Mühlfeldner
Roman Mühlfeldner

Posted on • Updated on • Originally published at r.muehlfeldner.com

How to fix ngx-translate error when running tests

Disclaimer
This is my first blog post. If you find anything that can be improved, don't hesitate and contact me! :)

Introduction

If you need to translate your application to different languages there are different opportunities to achieve this. One option for Angular application is the ngx-translate library.

The error

When I was testing my angular application which uses the ngx-translate library I got the following error:

Failed: Template parse errors:
The pipe 'translate' could not be found 
Enter fullscreen mode Exit fullscreen mode

This error says cleary, that the test can't find the translate pipe.
So, how can this be fixed?

Setup

To reproduce the error start a new angular application and add a minimal ngx-translate setup

Create the application

If you haven't done it yet install the angular cli globally:

npm install @angular/cli -g
Enter fullscreen mode Exit fullscreen mode

When the angular cli is installed a new application can be created:

ng new demo-testing --routing false --style scss --dry-run
cd ./demo-testing
Enter fullscreen mode Exit fullscreen mode

There should be three generated tests that succeed:

npm run test
Enter fullscreen mode Exit fullscreen mode

Add ngx-translate

The next step is to add the ngx-translate library.

First, @ngx-translate/core and @ngx-translate/http-loader have to be installed:

npm install @ngx-translate/core @ngx-translate/http-loader --save
Enter fullscreen mode Exit fullscreen mode

Setup the TranslateModule in the app.module.ts file:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule, HttpClient } from '@angular/common/http';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';

import { AppComponent } from './app.component';

export function createTranslateLoader(http: HttpClient) {
  return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: (createTranslateLoader),
        deps: [HttpClient]
      }
    })
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
Enter fullscreen mode Exit fullscreen mode

Add the TranslateService to the app.component.ts file and set the default language as well use it:

constructor(
  private readonly translate: TranslateService
) {
  this.translate.setDefaultLang('en');
  this.translate.use('en');
}
Enter fullscreen mode Exit fullscreen mode

Create this file: ./src/assets/i18n/en.json
This file contains the actual translations. For testing purposes one translation is enough:

{
    "WELCOME": "Welcome translate"
}
Enter fullscreen mode Exit fullscreen mode

The angular cli creates an example HTML page containing a toolbar with "Welcome" in the app.component.html file:

<span>Welcome</span>
Enter fullscreen mode Exit fullscreen mode

The static Welcome message can be replaced with the translation:

<span>{{ 'WELCOME' | translate }}</span>
Enter fullscreen mode Exit fullscreen mode

Run the application

To see if everything's working just start the application:

npm run start
Enter fullscreen mode Exit fullscreen mode

When you open localhost:4200 you should see the default angular app with a toolbar saying Welcome translate.

How to fix the tests

It's time to come back to the error mentioned at the beginning.

npm run test
Enter fullscreen mode Exit fullscreen mode

This will run the tests and print out the following error:

Failed: Template parse errors:
The pipe 'translate' could not be found 
Enter fullscreen mode Exit fullscreen mode

Explanation

The error above states out clearly that it can't find the translate pipe, which is provided in the app.module.ts file through the TranslateModule

In Angular, the unit tests in the *.spec.ts files are and should be isolated. Therefore, the tests won't import the modules defined in the app.module.ts file and don't even know there's a TranslateModule.

That's why the services provided by TranslateModule have to be mocked!

Mock the translate pipe

To mock the translate pipe open the app.component.spec.ts file and add a new custom pipe TranslatePipeMock before the describe function:

// ...omitted

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'translate'
})
export class TranslatePipeMock implements PipeTransform {
  public name = 'translate';

  public transform(query: string, ...args: any[]): any {
    return query;
  }
}

// omitted ...
Enter fullscreen mode Exit fullscreen mode

Next step is to add it to the declarations array and provide it:

// ... omitted

describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent,
        TranslatePipeMock // Add it to declarations
      ],
      // provide TranslatePipeMock
      providers: [
        {
          provide: TranslatePipe,
          useClass: TranslatePipeMock
        }
      ]
    }).compileComponents();
  }));

// omitted ...
Enter fullscreen mode Exit fullscreen mode

Save the file and run the tests again:

npm run test
Enter fullscreen mode Exit fullscreen mode

The error which says that it can't find the translate pipe should be gone. And another error appeared, stating there's no provider for TranslateService. This error can be solved just like the translate pipe error. Mock it and provide it.

Recapitulate the pipe mock implementation

To fix the error the TranslatePipe has to be mocked and provided inside the test file app.component.spec.ts.
What does this mean? As soon as we create another component that uses the TranslateModule the TranslatePipe has to be mocked again inside the test file of the newly created component. First intention would be to just copy and paste the TranslatePipeMock from the app.component.spec.ts file to the new test file.
But... Don't Repeat Yourself (DRY)

Create a reusable TranslateTestingModule

To prevent copying and pasting the TranslatePipeMock a TranslateTestingModule can be created which imports and exports it. And not just the TranslatePipeMock but also other mocked ngx-translate services, like the TranslateService mentioned above.

Implement the module

The first step is to generate a new module called TranslateTesting:

ng g module TranslateTesting
Enter fullscreen mode Exit fullscreen mode

Inside translate-testing.module.ts the mock classes will be implemented and provided.

import { Injectable, NgModule, Pipe, PipeTransform } from '@angular/core';
import { TranslateLoader, TranslateModule, TranslatePipe, TranslateService } from '@ngx-translate/core';
import { Observable, of } from 'rxjs';


const translations: any = {};

class FakeLoader implements TranslateLoader {
  getTranslation(lang: string): Observable<any> {
    return of(translations);
  }
}

@Pipe({
  name: 'translate'
})
export class TranslatePipeMock implements PipeTransform {
  public name = 'translate';

  public transform(query: string, ...args: any[]): any {
    return query;
  }
}

@Injectable()
export class TranslateServiceStub {
  public get<T>(key: T): Observable<T> {
    return of(key);
  }

  public addLangs(langs: string[]) { }

  public setDefaultLang(lang: string) { }

  public getBrowserLang(): string {
    return 'en';
  }

  public use(lang: string) { }
}

@NgModule({
  declarations: [
    TranslatePipeMock
  ],
  providers: [
    {
      provide: TranslateService,
      useClass: TranslateServiceStub
    },
    {
      provide: TranslatePipe,
      useClass: TranslatePipeMock
    },
  ],
  imports: [
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useClass: FakeLoader
      },
    })
  ],
  exports: [
    TranslatePipeMock,
    TranslateModule
  ]
})
export class TranslateTestingModule { }

Enter fullscreen mode Exit fullscreen mode

This file should cover all services provided by the TranslateModule

Use the new TranslateTestingModule

Now, the TranslatePipeMock class can be removed from the app.component.spec.ts file. Instead, the TranslateTestingModule will be imported:

// ... omitted
import { TranslateTestingModule } from './translate-testing/translate-testing.module';


describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent,
      ],
      imports: [
        TranslateTestingModule
      ]
    }).compileComponents();
  }));

// omitted ...
Enter fullscreen mode Exit fullscreen mode

Run the tests again and check if there are any ngx-translate errors left. The only errors you should see are angulars default tests which just check the title. These can be safely removed since translated text won't show up when running the tests.

npm run tests
Enter fullscreen mode Exit fullscreen mode

Conclusion

ngx-translate provides some services to translate text. These have to be mocked inside the test files. For that, we created a reusable module called TranslateTestingModule which can be imported to different test files. The Module can be located inside a shared folder, a testing folder or another shared library you use.

You can find a working example in a Github Repository

In case anything's unclear just ask me, please :)

Feel free to contact me on Twitter or LinkedIn.

Top comments (4)

Collapse
 
lakshmihariharan profile image
lakshmi-hariharan

Thanks for the article.. saved my day!

Collapse
 
appfield profile image
Roman Mühlfeldner

Awesome, thanks for letting me know!

Collapse
 
arielgueta profile image
Ariel Gueta

Thanks for the article. You are getting it for free when working with Transloco.

Collapse
 
appfield profile image
Roman Mühlfeldner

Thanks for your response. I didn't know about transloco. I will check it out!