I've followed the other examples here but I'm stumped with how to load react-18next into my Jest setup. I want to be able to verify that a component can render which is using translate.
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './i18n';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
});
TypeError: Cannot read property 'type' of undefined
at I18n.use (node_modules/i18next/dist/commonjs/i18next.js:237:16)
import React, { Component } from 'react';
jest.mock('react-i18next', () => ({
translate: () => Component => props => <Component t={() => ''} {...props} />
}));
What I _think_ is happening is that i18next-xhr-backend isn't working with Jest therefore my translation files aren't loading...but I'm not sure how to resolve this.
import i18n from 'i18next';
import Backend from 'i18next-xhr-backend';
import { reactI18nextModule } from 'react-i18next';
i18n
.use(Backend)
.use(reactI18nextModule)
.init({
interpolation: {
// React already does escaping
escapeValue: false
},
lng: 'en',
fallbackLng: 'en',
backend: {
loadPath: './locales/{{lng}}/{{ns}}.json'
},
debug: true,
react: {
wait: true
}
});
export default i18n;
i guess one of those imports is not working as expected -> Backend or reactI18nextModule is null -> please check that.
Beside that setting lng = cimode will help you to return keys instead of translations making testing more easy.
In the interest of narrowing down the issue I completely removed the backend code and I'm loading my translations via resources. I'm mocking react-i18next with the Jest configuration so I thought that would take care of it not loading however the line that is causing the error in node_modules/i18next/dist/commonjs/i18next.js is module.type...
import i18n from 'i18next';
import translationEnglish from '../public/locales/en/translation.json';
i18n
.init({
fallbackLng: 'cimode',
debug: false,
saveMissing: false,
interpolation: {
escapeValue: false // React already does escaping
},
resources: {
en: {
translation: translationEnglish
}
},
react: {
wait: false,
nsMode: 'fallback'
}
});
export default i18n;
like said:
TypeError: Cannot read property 'type' of undefined
at I18n.use (node_modules/i18next/dist/commonjs/i18next.js:237:16)
points to passing a undefined or null to: https://github.com/i18next/i18next/blob/master/src/i18next.js#L156
I understand that part, what I don't understand is why? I've followed all of the i18next docs and I'm still not passing the tests so how do I fix it?
I've made some headway by changing my codebase to utilize I18nextProvider instead.
import React from 'react';
import ReactDOM from 'react-dom';
import { I18nextProvider } from 'react-i18next';
import App from './App';
import i18n from './i18n-test';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(
<I18nextProvider i18n={i18n}>
<App />
</I18nextProvider>,
div
);
});
However now Jest is complaining about <I18nextProvider> being undefined. My application works perfectly but the tests don't pass.
Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
According to this Stack Overflow post it has to do with improperly importing/exporting the component.
so you will need to find out how to correctly import it for jest...that's the point. might be you need to import default (which webpack does automatically)...
so honestly not much i can do i guess.
Okay thanks
Spent all day trying to figure this out, there are only two ways to import a component, in this case I18nextProvider is a named component so the correct way is as I already have it import { I18nextProvider } from 'react-i18next';. If it was a default component then I would import it without the curly braces.
Jest follows the same conventions, which is why this works import React, { Component } from 'react'; so I'm at a loss as to why this isn't working. Is there anyone using Jest to test i18next??? I can't be the only person having this issue.
At the top of my Jest Configuration I now have
import { I18n, I18nextProvider } from 'react-i18next';
Which has somewhat progressed the error to...
Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
This leads me to believe that the issue is in how you're exporting the components from react-i18next and not how I'm importing them.
Nope....i guess your're using Trans component or other components in react-i18next -> means you need to mock those too:
https://github.com/i18next/react-i18next/tree/master/example/test-jest
https://github.com/i18next/react-i18next/blob/master/example/test-jest/__mocks__/react-i18next.js
Took me an hour to set this up...hopefully you consider using https://locize.com in future (for all that help you got ;))
guess this could be closed...if still got any issues beside using with jest...let me know.
Thank you for your help with this, settling on using Enzyme certainly helps and I was able to get it working with shallow rendering.
I will certainly be looking into your service when we are ready to start translating. As of right now I'm just trying to get the application up and running with some preliminary tests passing.
import React from 'react';
import { shallow } from 'enzyme';
import MyComponent from './MyComponent';
import { I18n } from 'react-i18next';
describe('MyComponent', function() {
it('should render without throwing an error', function() {
expect(
shallow(<MyComponent />)
.contains(
<I18n>
{(t) => (
<div className="content-heading">
{t('sidebar.nav.dashboard.MY_COMPONENT')}
</div>
)}
</I18n>
)
)
.toBe(true);
});
});
For anyone that comes here looking for an answer, here is an update now that I understand i18n, jest & enzyme a little better. In my example above shallow rendering only worked because it doesn't see anything inside of <I18n>.
_Note: I am using the render props method NamespacesConsumer vs. the hoc withNamespaces, IMHO it is much easier to comprehend and I don't understand why the hoc method is still the "recommended" way to use this library._
mount is the way to go if you actually want to write some substantial tests.i18n.js file used specifically for testing, in my case I use i18nTests.jsreact-i18next to take full advantage of NamespacesConsumer (replaced I18n component). Follow documentation on how to update your app for this.<I18nextProvider> and be sure to pass in the prop i18n which you get from i18nTests.js. Because Enzyme is only mounting this component, it using your index.js which wraps the app with it.index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { I18nextProvider } from 'react-i18next';
import App from './App';
import i18n from './i18n';
ReactDOM.render(
<I18nextProvider i18n={i18n}>
<App />
</I18nextProvider>,
document.getElementById('app')
);
MyComponent.test.js
import React from 'react';
import { mount } from 'enzyme';
import { I18nextProvider, NamespacesConsumer } from 'react-i18next';
import i18n from './i18nTests';
import MyComponent from './MyComponent';
describe('MyComponent', () => {
it('mounts without throwing an error', () => {
const wrapper = mount(
<I18nextProvider i18n={i18n}>
<MyComponent />
</I18nextProvider>
);
expect(wrapper.contains(
<NamespacesConsumer>
{(t) => (
<div className="content-heading">
{t('page_titles.MY_COMPONENT')}
</div>
)}
</NamespacesConsumer>
)).toBe(true);
});
});
@CWSites Did you actually archieve to test a component wrapped with withNamespaces hoc ?? Can't find any documentation for this.
@Renaud009
https://github.com/i18next/react-i18next/blob/master/test/withTranslation.spec.js -> is v10 but principle stays the same
v10: https://github.com/i18next/react-i18next/tree/master/example/test-jest
v9: https://github.com/i18next/react-i18next/tree/master/example/v9.x.x/test-jest
@jamuhl Thank you I just figured this out, I'am currently using next-i18next which still uses v9 so I was a bit confused 馃憤
@Renaud009 yup I did :)
My solution for this:
jest.mock('react-i18next', () => {
return {
withTranslation: x => y => y,
Trans: ({ children }) => children,
};
});
I have two simple use cases where I have to use <Trans /> (and with typescript) in my code base, and these have worked for each of my two cases:
A simpler case:
jest.mock('react-i18next', () => ({
useTranslation: (): {} => ({ t: (key: string): string => key }),
Trans: (): ReactElement => <></>,
}));
A slightly more complicated case:
type TransObject = {
i18nKey: string,
components: ReactElement[],
values: {}
}
jest.mock('react-i18next', () => ({
useTranslation: (): {} => ({ t: (key: string): string => key }),
Trans: (transElement: TransObject): ReactElement => (transElement.components[0])
}));
If you aren't using <Trans /> then you don't even need to mock that bit.
If you need the mock translate function to actually return translated values you could set it up like this:
import { translations as mockTranslations } from './path/to/translations';
// mockTranslations = { en: { helloWorld: 'Hello World!' } }
type UseTranslationHook = {
t: (key: string) => string;
};
// Mock the "useTranslation" hook.
jest.mock('react-i18next', () => ({
useTranslation: (): UseTranslationHook => ({
t: (key) => mockTranslations.en[key],
}),
}));
@KlavierCat Hi, i tried your 'simple case', which actually prevents <Trans> from rendering in tests. While it gets rid off of the issue, it does not render that element (just to point out). For 'slight complicated case' well, i get error that you can't access to the element [0] of the undefined. Am I missing something?
@SuperAL Hi, I tried with your mock like this,
const translation= JSON.stringify({
"flow": {
"page": {
"text": "First paragraph<1/><2/>Second paragraph<4/><5/>Last paragraph",
},
],
});
jest.mock('react-i18next', () => ({
withTranslation: () => (Component: any) => {
Component.defaultProps = {...Component.defaultProps, t: (translation: string) => translation};
return Component;
},
Trans: ({children}) => children,
}));
and it resulted with this in the snapshot, which i guess is as expected.
<br />
<br />
text
<br />
<br />
text
My <Trans> usage looks like:
<Trans i18nKey="flow.page.text" ns="translation">
text
<br />
<br />
text
<br />
<br />
text
</Trans>
Most helpful comment
For anyone that comes here looking for an answer, here is an update now that I understand i18n, jest & enzyme a little better. In my example above
shallowrendering only worked because it doesn't see anything inside of<I18n>._Note: I am using the render props method
NamespacesConsumervs. the hocwithNamespaces, IMHO it is much easier to comprehend and I don't understand why the hoc method is still the "recommended" way to use this library._mountis the way to go if you actually want to write some substantial tests.i18n.jsfile used specifically for testing, in my case I usei18nTests.jsreact-i18nextto take full advantage ofNamespacesConsumer(replacedI18ncomponent). Follow documentation on how to update your app for this.<I18nextProvider>and be sure to pass in the propi18nwhich you get fromi18nTests.js. Because Enzyme is only mounting this component, it using yourindex.jswhich wraps the app with it.index.js
MyComponent.test.js