Search code examples
javascriptangularjestjskarma-jasminei18next

Testing i18next complain about "Cannot read property 'use' of undefined"


I have use the jest mock

    jest.mock('i18next', () => ({
        use: () => this,
        init: () => { },
        t: k => k
      }));

Didn't resolve the issue. I am getting TypeError: Cannot read property 'use' of undefined

But if I use require module instead import for i18next will be fine

const i18next = require('i18next'); // works
import i18next from 'i18next'; // will fail jest test with TypeError: Cannot read property 'use' of undefined

And tried to google the solution and cannot find a solution works for me.

import i18next from 'i18next'
import languageDetector from "i18next-browser-languagedetector";
import { languageData } from './language.resources';

export class Translation {
  private static _instance: Translation;
  isInitialized : boolean = false;  
  private resources = languageData;
  private detectOptions = {
    order: ['navigator', 'localStorage', 'sessionStorage', 'cookie', 'htmlTag', 'path', 'subdomain'],
    lookupLocalStorage: 'lng'
  }

  constructor() {
  }

  setLanguageResources(data) {
     this.resources = data;     
  } 

  initi18n() {
    i18next
    .use(languageDetector)
    .init({
      resources: this.resources,
      fallbackLng: 'en-US',
      detection: this.detectOptions,
      debug: true,
      returnEmptyString: false,
      ns: [
        'translation',
        'validation',
        'error'          
      ],
    });
    this.isInitialized = true;
  }

  get i18nInstance() {  
      return i18next;
  }

  translate(key : string) {
    return i18next.t(key);
  }

  static getInstance() {
    if(this._instance) {
      return this._instance;
    }

    this._instance = new Translation();
    this._instance.initi18n();
    return this._instance;
  }
}

Test spec file

import * as i18next from 'i18next';
import { Translation } from './translation.utility';

describe('Translation', () => {      

    jest.mock('i18next', () => ({
        use: () => this,
        init: () => { },
        t: k => k
      }));


    it('should create an instance', () => {
      expect(new Translation()).toBeTruthy();
    });

    it('should test setResource', () => {
        let translate = new Translation();
        translate.setLanguageResources({});
        expect(translate["resources"]).toEqual({})        
    });

    it('should test new Translation singleton object', () => {
        let translate = new Translation();
        translate.setLanguageResources({});
        expect(translate["resources"]).toEqual({})    
        
        translate = Translation.getInstance();
        expect(translate.isInitialized).toEqual(true);
    });

    it('should test singleton', () => {
        let translate = Translation.getInstance();
        translate.setLanguageResources({});
        expect(translate.isInitialized).toEqual(false);
        expect(translate["resources"]).toEqual({})        
    });

    xit('should test init i18next', () => {
        let translate = new Translation();
        translate.i18nInstance;
        expect(translate.translate("")).toBeDefined();        
    });
})

jest.config.js file

module.exports = {
  name: "translation",
  preset: "../../jest.config.js",
  setupFilesAfterEnv: ["<rootDir>/src/test-setup.ts"],
  globals: {
    "ts-jest": {
      tsConfig: "<rootDir>/tsconfig.spec.json",
      stringifyContentPathRegex: "\\.(html|svg)$",
      astTransformers: [
        "jest-preset-angular/build/InlineFilesTransformer",
        "jest-preset-angular/build/StripStylesTransformer",
      ],
    },
  },
  coverageDirectory: "../../coverage/libs/translation",
  snapshotSerializers: [
    "jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js",
    "jest-preset-angular/build/AngularSnapshotSerializer.js",
    "jest-preset-angular/build/HTMLCommentSerializer.js",
  ],
};

tsconfig.lib.json

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "../../dist/out-tsc",
    "target": "es2015",
    "declaration": true,
    "declarationMap": true,
    "inlineSources": true,
    "module": "commonjs",
    "types": [],
    "lib": ["dom", "es2018"]
  },
  "angularCompilerOptions": {
    "skipTemplateCodegen": true,
    "strictMetadataEmit": true,
    "enableResourceInlining": true
  },
  "exclude": ["src/test-setup.ts", "**/*.spec.ts"],
  "include": ["**/*.ts"]
}


Solution

  • I was able to fix a similar issue by changing how I mocked i18n.

    Here's what I used:

    jest.mock('../../src/i18n', () => ({
        __esModule: true,
        use: () => {},
        init: () => {},
        default: {
            t: (k) => k,
        },
    }));
    

    In my case, I was importing i18next within my src/i18n.ts file, so that's why I mocked it with that string. You may be able to use i18next instead of '../../src/i18n'.

    Hope this helps.