使用useTranslation进行next-i18next Jest测试

22

测试库总是很有趣。我正在我的NextJS项目中使用next-i18next。我们正在使用带有命名空间的useTranslation钩子。

当我运行测试时,会出现以下警告:

console.warn react-i18next:您需要通过使用initReactI18next传递i18next实例

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

我尝试了react-i18next测试例子的设置,但没有成功。我还尝试了这个建议

此外,我还尝试了模拟useTranslation,但也没有成功。

有没有更简单的解决方案来避免这个警告?就测试而言,测试已经通过了...

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');
  });

最后,是否有办法让翻译后的纯文本成为结果,而不是带命名空间的:account:account:organization.invite.copyLink

1
你找到解决方案了吗? - nhaht
1
我可能会研究一下 https://github.com/vinissimus/next-translate。 - Phil Lucks
现在怎么样?你找到解决方案了吗? - Urmzd
7个回答

16
在describe块之前或beforeEach()中使用以下代码段来模拟所需的内容。
jest.mock("react-i18next", () => ({
    useTranslation: () => ({ t: key => key }),
}));

希望这能帮到你。祝平安。

2
对我来说,在describe()块之前放置它就可以了。 - Juanma Menendez
是的,将这个放到“beforeAll”或其他钩子中都行不通,但另一个评论所说的对我有用。 - ForestG
我把它放在我的 jest.setup.js 文件中,同时我稍微修改了 t 函数的模拟,使其能够接受参数:t: (key, parameters) => (parameters ? key + JSON.stringify(parameters) : key) - undefined

3
我找到了一种方法,使用renderHook函数和react-i18next中的useTranslation钩子来使用i18next实例使测试工作。这是我想要测试的Home组件:

import { useTranslation } from 'next-i18next';

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

export default Home;

首先,我们需要为jest创建一个设置文件,以便我们可以启动i18n实例并将翻译导入配置。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;

然后,我们将设置文件添加到我们的 jest.config.js 中:

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

现在我们可以使用I18nextProvideruseTranslation钩子来尝试我们的测试:

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();
  });

});

这里我们使用了I18nextProvider,并使用useTranslation钩子发送i18n实例。之后,在Home组件中顺利加载了翻译。
我们还可以运行changeLanguage()函数来更改所选语言,并测试其他的翻译。

1

使用此函数替换渲染函数。


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("")

    })
})



目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community

0

我使用了比Mock更为复杂的方法,以确保所有的函数在测试和生产环境中运作相同。

首先,我创建了一个测试环境:

// 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();
    /* The important part start */
    const i18nInstance = i18next.createInstance();
    await i18nInstance.use(initReactI18next).init({
      lng: "cimode",
      resources: {},
    });
    this.global.i18nInstance = i18nInstance;
    /* The important part end */
  }
}

我在 jest.config.ts 中添加了这个环境:

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

示例组件:

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

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

之后我会在测试中使用它:

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

it("renders Component", () => {
  /* The important part start */
  setI18n(global.i18nInstance);
  /* The important part end */
  let root: ReactTestRenderer;
  act(() => {
    root = create(<Component />);
  });
  expect(root.toJSON()).toMatchSnapshot();
});

0
对于那些在图书馆本身找不到解决方案或任何帮助的人来说,这就是我最终采取的方法来加载和测试翻译内容。
我正在使用 Next JS v12.x 和 next-i18next v12.1.0,以及 jest 和 testing-library,但也适用于其他环境。
  1. 我添加了一个测试实用程序文件 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 i18n instance with the given namespaces.
 * @param {string[]} namespaces - An array of namespaces.
 * @param {string} locale - The locale to use.
 * @returns {i18n.i18n} The initialized i18n instance.
 */
const initializeI18n = async (
  namespaces: string[],
  locale = DEFAULT_LOCALE
) => {
  const resources: { [ns: string]: object } = {};

  // Load resources for the default language and given namespaces
  namespaces.forEach((ns) => {
    const filePath = `public/locales/${locale}/${ns}.json`;
    try {
      // eslint-disable-next-line @typescript-eslint/no-var-requires,global-require,import/no-dynamic-require
      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;

在我的测试文件中,我使用异步方式初始化了实例,并加载了我想要的命名空间,渲染了我的组件,并等待屏幕找到渲染后的翻译文本。
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();
  });
});

希望能有所帮助。

0
我通过以下步骤使其正常工作: 将以下内容添加到你的package.json文件中。
"jest": {
  "setupFiles": [
    "./jestSetupFile.js"
  ]
}

然后将此内容添加到 jestSetupFile.js 中。
jest.mock("react-i18next", () => ({
  useTranslation: () => {
    return {
      t: (str) => str,
    };
  },
}));

0
关于你上次的问题,我找到了一种将命名空间转换为其实际翻译值的方法。由于某种原因,包装提供者对我来说从未起作用,因此我不得不研究这种新的替代方法。
文档中的原始模拟:
jest.mock('react-i18next', () => ({
 
  useTranslation: () => {
    return {
      t: (str) => str,
      i18n: {
        changeLanguage: () => new Promise(() => {}),
      },
    };
  },
  initReactI18next: {
    type: '3rdParty',
    init: () => {},
  }
}));

我们需要以某种方式将t(str)=> str转换为返回翻译后的值而不是命名空间。请注意,它返回一个字符串。因此,我们必须以某种方式将"button.proceed"转换为englishFile["button"]["proceed"]。通过这样做,它将返回翻译后的值。
这个函数就是这样做的。
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;
};

现在,新的模拟应该看起来像这样,
jest.mock("react-i18next", () => ({
    useTranslation: () => {
        // translation files should be placed here to avoid error warnings.
        const enFile = jest.requireActual("../../locales/en.json");
        return {
            t: (key) => translate(key, enFile),
            i18n: {
                changeLanguage: () => new Promise(() => {}),
            },
        };
    },
    initReactI18next: {
        type: "3rdParty",
        init: () => {},
    },
    I18nextProvider: ({ children }) => children,
}));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接