Testing next-i18next localization with useTranslation in Jest

Exploring testing libraries is always an interesting adventure. In my NextJS project, I'm utilizing next-i18next. We're incorporating the useTranslation hook with namespaces.

During testing, a warning pops up:

console.warn react-i18next:: You will need to pass in an i18next instance by using initReactI18next

> 33 |   const { t } = useTranslation(['common', 'account']);
     |                 ^

I've attempted the setup recommended in the react-i18next test examples to no avail. Also, tried this suggestion mentioned here.

Tried mocking useTranslation as well without success.

Is there a simpler solution to eliminate this warning? For your information, the test does pass...

test('feature displays error', async () => {
    const { findByTestId, findByRole } = render(
      <I18nextProvider i18n={i18n}>
        <InviteCollectEmails onSubmit={jest.fn()} />
      </I18nextProvider>,
      {
        query: {
          orgId: 666,
        },
      }
    );

    const submitBtn = await findByRole('button', {
      name: 'account:organization.invite.copyLink',
    });

    fireEvent.click(submitBtn);

    await findByTestId('loader');

    const alert = await findByRole('alert');
    within(alert).getByText('failed attempt');
  });

Lastly, is it possible to have the translated plain text as the result instead of the namespaced version like:

account:account:organization.invite.copyLink
?

Answer №1

If you want to mock the necessary functionality, you can insert this code snippet before your describe block or within beforeEach().

jest.mock("react-i18next", () => ({
    useTranslation: () => ({ t: key => key }),
}));

I trust this information proves useful to you. Take care.

Answer №2

After doing some research and referring to previous answers, I successfully managed to get the tests working with an instance of i18next by utilizing the renderHook function along with the useTranslation hook from react-i18next.

This is the Home component that I aimed to test:

import { useTranslation } from 'next-i18next';

const Home = () => {
  const { t } = useTranslation("");
  return (
    <main>
      <div>
        <h1> {t("welcome", {ns: 'home'})}</h1>
      </div>
    </main>
  )
};

export default Home;

In order to start testing, we first need to create a setup file for jest to initiate an i18n instance and import translations into the configuration. The file is named test/setup.ts

import i18n from "i18next";
import { initReactI18next } from "react-i18next";

import homeES from '@/public/locales/es/home.json';
import homeEN from '@/public/locales/en/home.json';

i18n.use(initReactI18next).init({
  lng: "es",
  resources: {
    en: {
      home: homeEN,
    },
    es: {
      home: homeES,
    }
  },
  fallbackLng: "es",
  debug: false,
});

export default i18n;

Next, we include the setup file in our jest.config.js:

setupFilesAfterEnv: ["<rootDir>/test/setup.ts"]

We can now proceed to run our tests using the I18nextProvider and the useTranslation hook:

import '@testing-library/jest-dom/extend-expect';
import { cleanup, render, renderHook } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import { I18nextProvider, useTranslation } from 'react-i18next';

import Home from '.';

describe("Index page", (): void => {
  afterEach(cleanup);

  it("should render properly in Spanish", (): void => {
    const t = renderHook(() => useTranslation());

    const component = render( 
      <I18nextProvider i18n={t.result.current.i18n}>
        <Home /> 
      </I18nextProvider>
    );

    expect(component.getByText("Bienvenido a Pocky")).toBeInTheDocument();
  });

  it("should render properly in English", (): void => {
    const t = renderHook(() => useTranslation());
    act(() => {
      t.result.current.i18n.changeLanguage("en");
    });

    const component = render( 
      <I18nextProvider i18n={t.result.current.i18n}>
        <Home/>
      </I18nextProvider>
    );
    expect(component.getByText("Welcome to Pocky")).toBeInTheDocument();
  });

});

We utilized the I18nextProvider and passed the i18n instance using the useTranslation hook. Subsequently, the translations were successfully loaded into the Home component without any issues.

We also tested changing the selected language by invoking the changeLanguage() function and examining the other translations.

Answer №3

Need a replacement for the render function? Here's a solution:


import { render, screen } from '@testing-library/react'
import DarkModeToggleBtn from '../../components/layout/DarkModeToggleBtn'
import { appWithTranslation } from 'next-i18next'
import { NextRouter } from 'next/router'


jest.mock('react-i18next', () => ({
    I18nextProvider: jest.fn(),
    __esmodule: true,
 }))
  
const createProps = (locale = 'en', router: Partial<NextRouter> = {}) => ({
    pageProps: {
        _nextI18Next: {
        initialLocale: locale,
        userConfig: {
            i18n: {
            defaultLocale: 'en',
            locales: ['en', 'fr'],
            },
        },
        },
    } as any,
    router: {
        locale: locale,
        route: '/',
        ...router,
    },
} as any)

const Component = appWithTranslation(() => <DarkModeToggleBtn />)

const defaultRenderProps = createProps()

const renderComponent = (props = defaultRenderProps) => render(
    <Component {...props} />
)


describe('', () => {
    it('', () => {

        renderComponent()
     
        expect(screen.getByRole("button")).toHaveTextContent("")

    })
})

Answer №4

To ensure consistent functionality in both testing and production environments, I implemented a more advanced approach rather than simply mocking the functions.

Firstly, I set up a testing environment:

// testing/env.ts
import i18next, { i18n } from "i18next";
import JSDomEnvironment from "jest-environment-jsdom";
import { initReactI18next } from "react-i18next";

declare global {
  var i18nInstance: i18n;
}

export default class extends JSDomEnvironment {
  async setup() {
    await super.setup();
    /* Key section begins */
    const i18nInstance = i18next.createInstance();
    await i18nInstance.use(initReactI18next).init({
      lng: "cimode",
      resources: {},
    });
    this.global.i18nInstance = i18nInstance;
    /* Key section ends */
  }
}

I included this testing environment in jest.config.ts:

// jest.config.ts
export default {
  // ...
  testEnvironment: "testing/env.ts",
};

An example component is shown below:

// component.tsx
import { useTranslation } from "next-i18next";

export const Component = () => {
  const { t } = useTranslation();
  return <div>{t('foo')}</div>
}

In the subsequent tests, I utilized this setup:

// component.test.tsx
import { setI18n } from "react-i18next";
import { create, act, ReactTestRenderer } from "react-test-renderer";
import { Component } from "./component";

it("renders Component", () => {
  /* Key section begins */
  setI18n(global.i18nInstance);
  /* Key section ends */
  let root: ReactTestRenderer;
  act(() => {
    root = create(<Component />);
  });
  expect(root.toJSON()).toMatchSnapshot();
});

Answer №5

After struggling to find a solution or any support within the library, I finally managed to successfully load and test the translations using the following method.

I am currently working with Next JS v12.x and next-i18next v12.1.0 in conjunction with jest and testing-library, but this approach can be adapted to other environments as well.

  1. To start, I created a testing utility file src/utils/testing.ts
/* eslint-disable import/no-extraneous-dependencies */
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';

import { DEFAULT_LOCALE } from 'src/utils/constants';

/**
 * Initializes the instance of i18n with specified namespaces.
 * @param {string[]} namespaces - Array of namespaces.
 * @param {string} locale - Desired locale.
 * @returns {i18n.i18n} Initialized i18n instance.
 */
const initializeI18n = async (
  namespaces: string[],
  locale = DEFAULT_LOCALE
) => {
  const resources: { [ns: string]: object } = {};

  // Loading resources for default language and given namespaces
  namespaces.forEach((ns) => {
    const filePath = `public/locales/${locale}/${ns}.json`;
    try {
      const translations = require(`../../${filePath}`);
      resources[ns] = translations;
    } catch (error) {
      throw new Error(
        `Could not load translations for locale: ${locale}, namespace: ${ns}`
      );
    }
  });

  await i18n.use(initReactI18next).init({
    lng: locale,
    fallbackLng: locale,
    debug: false,
    ns: namespaces,
    defaultNS: namespaces[0],
    resources: { [locale]: resources },
    interpolation: { escapeValue: false },
  });

  return i18n;
};

export default initializeI18n;
  1. In my test file, I initialized the instance asynchronously with the desired namespace, rendered the component, and waited for the screen to display the translated text.
describe('when price is zero', () => {
  beforeEach(async () => {
    await initializeI18n(['common_areas']);

    render(<CommonAreaCard commonArea={mockCommonArea(0)} />);
  });

  it('should render the free price', async () => {
    expect(
      await screen.findByText('Sin costo de reservación')
    ).toBeInTheDocument();
  });
});

I trust that these steps will prove helpful to others facing similar challenges.

Answer №6

I managed to resolve the issue by taking the following steps: Include the below snippet in your package.json

"jest": {
  "setupFiles": [
    "./jestSetupFile.js"
  ]
}

Next, insert this code into your jestSetupFile.js

jest.mock("react-i18next", () => ({
  useTranslation: () => {
    return {
      t: (str) => str,
    };
  },
}));

Answer №7

In response to your previous inquiry, I have discovered a method to convert namespaces into their corresponding translated values. Despite my attempts at wrapping the provider proving ineffective, I delved into this new alternative solution.

Here is the initial mock provided by the documentation:

jest.mock('react-i18next', () => ({
 
  useTranslation: () => {
    return {
      t: (str) => str,
      i18n: {
        changeLanguage: () => new Promise(() => {}),
      },
    };
  },
  initReactI18next: {
    type: '3rdParty',
    init: () => {},
  }
}));

We need to modify t(str) => str to output the translation instead of the namespace. Note that it currently returns a string, so we must transform, for instance, "button.proceed" to englishFile["button"]["proceed"]. This alteration will yield the translated value.

The following function accomplishes this task.

const translate = (givenString, file) => {

const splitArr = givenString.split(".");
let result = file;
for (let i = 0; i < splitArr.length; i++) {
    result = result[splitArr[i]];
}
    return result;
};

Subsequently, the revised mock should resemble the example below,

jest.mock("react-i18next", () => ({
    useTranslation: () => {
        // Translation files must be included here to prevent error notifications.
        const enFile = jest.requireActual("../../locales/en.json");
        return {
            t: (key) => translate(key, enFile),
            i18n: {
                changeLanguage: () => new Promise(() => {}),
            },
        };
    },
    initReactI18next: {
        type: "3rdParty",
        init: () => {},
    },
    I18nextProvider: ({ children }) => children,
}));

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Navigating through each element of an array individually by using the onClick function in React

I'm currently working on a project that involves creating a button to cycle through different themes when pressed. The goal is for the button to display each theme in sequence and loop back to the beginning after reaching the last one. I've imple ...

Encountering a 404 Error while using my Next.js 13 API Route

I recently forked a project and attempted to set up an API Endpoint for my CRUD operations with a database. However, I encountered difficulties in accessing my endpoint. Even with a test on a dummy API https://jsonplaceholder.typicode.com/todos, I still re ...

Issue encountered when attempting to assign `fontWeight` within `makeStyles` using `theme` in Typescript: Error message states that the property is not

Currently, within my NextJS project and utilizing MUI, I am attempting to define a fontWeight property using the theme settings in the makeStyles function. Oddly enough, this issue only arises when building inside a docker container, as building locally po ...

What is the best way to sift through slug data?

Struggling to display related posts from the same category in my project using Sanity for slug data. Attempted fetching all data and storing it in the state but unsure how to filter based on the current post's category. I'm thinking about leverag ...

Error in NextJs: The text content does not align with the server-rendered content

I am working on a new project using "NextJs", "date-fns", and "React-Calendar". However, I am facing an issue with date rendering between the server side (nodejs =english format) and client side (french): Warning: Text content did not match. Server: "April ...

Creating an interactive /robots.txt file for a Next.js application

I'm looking for a solution to dynamically respond to the /robots.txt request. To achieve this, I have chosen to utilize getServerSideProps https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering By exporting a ...

Re-establishing the socket channel connection in a React application after a disconnection

There are several solutions to this issue, but none of them seem to be effective for me. The existing solutions are either outdated or do not meet my needs. I am facing a problem where I have a large amount of data being transferred from the server to the ...

Creating a Next.js application that retrieves mock data and dynamically presents it on the user interface

I've been attempting to retrieve some placeholder data from an API and showcase it on the screen, but unfortunately nothing is appearing. Interestingly, the data does show up in the console, just not on the actual screen. function Shop() { const [pr ...

The call to the hook is invalid. In order to use hooks, they must be called within the body of a function component in

So, in my upcoming application, I am incorporating the react-google-one-tap-login library which features the useGoogleOneTapLogin hooks that need to be invoked within a React component. However, when I attempt to use it in this manner: func() { useGoogle ...

Fetch data from Firestore when the page loads using the useEffect hook

Below is the simplified code snippet I am currently using: import { useState, useEffect, useContext } from 'react' import { useRouter } from 'next/router' import { firestore } from './firebase-config' import { getDoc, doc } f ...

Error: Unable to access attributes of null object (specifically 'disable click')

Currently, I am integrating react-leaflet with nextjs. Within the markers, there are popups containing buttons that open another page upon click. However, I have encountered an error when navigating to the new page: Unhandled Runtime Error TypeError: Canno ...

I need to obtain the URL pathname on a server component in Next.js version 13 - how is this achieved

I'm facing an issue with retrieving the pathname of a server component located in the app directory. Despite attempting to obtain it through window.location, I haven't been successful. Is there an alternative method I can use to achieve this? ...

Using jest to simulate a private variable in your code

I am working on unit testing a function that looks like this: export class newClass { private service: ServiceToMock; constructor () { this.service = new ServiceToMock() } callToTest () { this.service.externalCall().then(()=& ...

Is there a way to handle the redirection from getInitialProps in _error.js within Next.js?

Is there a method to redirect to a different URL from getInitialProps in _error.js in Next.js? I attempted using res.redirect('/'); inside getInitialProps. However, it resulted in TypeError: res.redirect is not a function ...

Nock does not capture the requests - Error: Failed to resolve address ENOTFOUND

Let me provide an overview of the structure in place. Jest is utilized for executing the testing process. Within my jest.config.json file, I incorporate the following line: "globalSetup": "<rootDir>/__tests__/setup.js", Inside setup.js, you will ...

Designing the File and Folder Organization for Next.js Frontend and AWS Cloud Development Kit (CDK) Backend

When it comes to creating websites with serverless backends, I've been thinking about the best practices for folder structure. Currently, my setup includes a Next.js frontend and an AWS CDK backend. The way I've structured the folders has the bac ...

The Next.js Link feature does not always guarantee that the component will render correctly, and the serverSideProps function may not always receive updated

Having an issue with next.js - when a user tries to navigate from one profile to another using the Link in the navbar: <li> <Link href={`/profile/${user.user.id}`}> <a className="flex flex-row items-center"> ...

Discover the power of utilizing the reduce function to extract the value of a specific field within an array of Objects

In the following example, we have an object with 3 forms: formA, formB, and formC. Form A and B are objects, while formC is an array of objects that can contain multiple items. const object: { "formA": { "details": {}, ...

Testing a Jest unit on a function that invokes another function which in turn returns a Promise

I have a function that triggers another function which returns a Promise. Below is the code snippet for reference: export const func1 = ({ contentRef, onShareFile, t, trackOnShareFile, }) => e => { trackOnShareFile() try { func2(conte ...

Discover the power of using the Apollo useMutation function within a customized React hook

I'm struggling to understand Hooks, particularly when I encounter an Invalid hook call error. The latest issue is with using the useMutation hook from Apollo within a custom hook. Can someone please help me figure out what's going wrong? Compone ...