4

I'm trying to stub/spy the translation, not just mock it, and I can't seem to get it to trigger even in this most base case.

/**
 * ComponentName.jsx
 */

import { useTranslation } from "react-i18next";

export default function ComponentName() {
  const { t } = useTranslation();

  return <div>t(`index:path`)</div>
}

/**
 * ComponentName.test.jsx
 */

import { shallow } from "enzyme";
import ComponentName from "./ComponentName";
import { useTranslation } from "react-i18next";
jest.mock("react-i18next", () => ({
  useTranslation: () => ({ t: jest.fn(key => key) })
}));

it("calls the translation function", () => {
  const wrapper = shallow(<ComponentName />);

  expect(useTranslation().t).toHaveBeenCalled();
});

When I drop a console.log(t) in the ComponentName.jsx file, it correctly displays that it's a mocked function. If I put t() in the ComponentName.test.jsx file, it passes.

Is there a way to stub this so that I can eventually tag it with toHaveBeenCalledWith? Or am I relegated to doing contains("index:path") on the component?


Edit: So, when I updated on @felixmosh's answer

/**
 * ComponentName.test.jsx
 */


import { mount } from 'enzyme';
import { I18nextProvider } from 'react-i18next';

describe('<SomeComponent />', () => {
  it('dispatches SORT_TABLE', () => {
    const i18nextMock = {
      t: jest.fn(key => key),
    };
    const enzymeWrapper = mount(
      <I18nextProvider i18n={i18nextMock}>
        <SomeComponent />
      </I18nextProvider>
    );

    expect(i18nextmock.t).toHaveBeenCalled()
  });
});

/**
 * ComponentName.jsx
 */

import { useTranslation } from "react-i18next";

export default function ComponentName() {
  const { t } = useTranslation();

  return <div>t(`index:path`)</div>
}

It's the same issue. If t was "a string" instead of jest.fn(), when i console.log t in ComponentName.jsx, I correctly get "a string", when I console.log t as jest.fn(key => key), I correctly get a function.

But when I call it, I don't get it.

Is it possible that it's not the same instance that's being sent to I18nextProvider?

FullOnFlatWhite
  • 3,642
  • 2
  • 18
  • 23

1 Answers1

4

2 Things that you should improve in your code:

  1. useTransaltion is a hook which requires context, make sure you wrap you component with i18nextProvider.
  2. As of the fact you will have nested components switch shallow with mount.
  3. There is no need for mocking anything, i18next has a built-it support of tests. In order to enable it use cimode as the lng when configuring your i18next for tests.
// i18nForTests.js

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

i18n.use(initReactI18next).init({
  lng: 'cimode',
  // ----- ^
  // have a common namespace used around the full app
  ns: ['translations'],
  defaultNS: 'translations',

  interpolation: {
    escapeValue: false, // not needed for react!!
  },

  resources: { en: { translations: {} } },
});

export default i18n;
// SomeComponent.test.js
import { mount } from 'enzyme';
import { I18nextProvider } from 'react-i18next';
import i18n from '../i18nForTests';

describe('<SomeComponent />', () => {
  it('dispatches SORT_TABLE', () => {
    const enzymeWrapper = mount(
        <I18nextProvider i18n={i18n}>
          <SomeComponent />
        </I18nextProvider>
    );
    enzymeWrapper.find('.sort').simulate('click');

    expect(enzymeWrapper.find('#some-text').text()).toEqual('MY_TRANS_KEY');
  });
});

Edit: Version with mocked I18next

// i18nextMock.js
export const i18nextMock = {
  t: jest.fn(),
  // Do the same for other i18next fields
};

// SomeComponent.test.js
import { mount } from 'enzyme';
import { I18nextProvider } from 'react-i18next';
import i18nextMock from '../i18nextMock';

describe('<SomeComponent />', () => {
  it('dispatches SORT_TABLE', () => {
    const enzymeWrapper = mount(
        <I18nextProvider i18n={i18nextMock}>
          <SomeComponent />
        </I18nextProvider>
    );
    enzymeWrapper.find('.sort').simulate('click');

    expect(enzymeWrapper.find('#some-text').text()).toEqual('MY_TRANS_KEY');
  });
});
felixmosh
  • 32,615
  • 9
  • 69
  • 88
  • How do you configure your `lng` for jest? – RyanP13 Sep 25 '20 at 19:14
  • I mean, yes, I understand that's a possibility, but I'm not asking to figure out if the result is correct, but that the translation method itself is called with the correct values by spying on it. – FullOnFlatWhite Sep 30 '20 at 14:01
  • Basically I'm ensuring that `t()` is called with the correct parameters, not that the result is correct, even though the result is the parameter. Example: I could literally just put `MY_TRANS_KEY` as the content of `#some-text`, and it would pass, and that's where the issue lies, I'm trying to remove that ambiguity. – FullOnFlatWhite Sep 30 '20 at 14:12
  • In that case, pass `mocked` i18n to the I18nextProvider, check the edited code – felixmosh Sep 30 '20 at 14:23