How to mock i18next and react-i18next for testing a component/view using typescript - reactjs

I basically have a react website and I'm using typescript for both the source and test code. I have integrated the i18next and react-i18next libraries to my project for supporting translations and modified one of my react components. However, I'm having issues when I try to unit test my components that are extending withTranslation. The first problem i'm having is i'm getting a compilation error, saying that my props are missing: i18n, tReady and t. And yes that is true they are not there, but I'm not sure how should I handle that.
I have tried including the mock below on my tests, which would supposedly pass the required props to my component:
jest.mock("react-i18next", () => ({
withTranslation: () => (Component: any) => {
Component.defaultProps = {
...Component.defaultProps,
t: () => "",
};
return Component;
},
}));
This is how my code looks like:
interface DemoViewProps extends WithTranslation {
name: string;
}
const DemoView = (props: DemoViewProps) => {
const { t, name } = props;
return <div>{t("string id")} {name}</div>
}
This is how my test code looks like:
describe("<DemoView>", () => {
const props = {
name: "NAME",
};
const component = shallow(<DemoView {...props} />);
it("should do something", () => {
...some testing here
});
});
I would like not to get compilation errors and be able to run my unit tests. I would also like my code not to become to cumbersome, since I will be doing this across different components that are using withTranslation()

Related

Mocking compound components with Jest

I have a compound component that looks like this:
export const Layout = Object.assign(Main, {
Top, Content, Center, Left, Right,
})
There is an object assigning Main to some other components so can use dot notation. Below you can see how I am trying to mock it inside the jest.setup.js file:
jest.mock(
'~/layouts/Layout', () => ({
__esModule: true,
default: (props) => {
return <mock-layout>{props.children}</mock-layout>
},
}),
)
Running jest --ci will return an error with the message Cannot read properties of undefined (reading 'Top')
In this topic I found a way to do something like this:
const component = (props) => <layout-mock>{props.children}</layout-mock>;
component.Top = (props) => <layout-mock-top>{props.children}</layout-mock-top>;
jest.mock(
'~/layouts/Layout', () => ({
__esModule: true,
default: component,
}),
)
But it gives me the same error.
How could I mock it properly? Do you have any ideas?
You will need to update the variable name to mockVariable for this to work in order to work around the out of scope variables error.
Details here: https://jestjs.io/docs/es6-class-mocks#calling-jestmock-with-the-module-factory-parameter

React how to test props passed to component passed as prop

Okay, this might sound complicated but it will be easy if you read the following example. The main purpose of this is to separate the logic from the actual render code. Making the component smaller and (in theory) easier to test.
class NameProvider {
public getName(): Promise<string> {
return Promise.resolve("Cool name");
}
}
interface RenderProps {
name: string;
onGetNamePress(): void;
}
interface LogicProps {
nameProvider: NameProvider;
render: React.ComponentType<RenderProps>
}
function Render({name, onGetNamePress}: RenderProps): React.ReactElement {
return <>
<p>{name}</p>
<button title="Get name!" onClick={onGetNamePress} />
</>
}
function Logic({nameProvider, render: Render}: LogicProps): React.ReactElement {
const [name, setName] = React.useState<string>();
return <Render
name={name}
onGetNamePress={fetch}
/>
async function fetch() {
setName(await nameProvider.getName());
}
}
Testing the render component is rather easy, but how do I test that the props passed to the render component are correct? Especially after the state changed.
Consider the following:
it('fetches the name after the button was pressed', () => {
const mnp = new MockNameProvider();
render(<Logic
nameProvider={mnp}
render={({name, onGetNamePress}) => {
act(async () => {
await onGetNamePress();
expect(name).toBe(mockName);
})
}}
/>)
})
This will cause an infinite loop, as the state keeps getting changed and the name fetched. I also couldn't imagine how to get the new props. This current code will test the old ones to my understanding. So my question is, how do I test if the props are correctly passed (also after updates).
(Important) Notes:
I'm actually writing a react native app, so maybe the issue is specific to native testing but I didn't think so.
This is not code from our codebase and just cobbled together. Thus also the React prefix, vscode just liked that better in an unsaved file.

How to correctly mock React Navigation's getParam method using Jest

I have a React Native app in which I'm trying to write some integration tests using Jest & Enzyme. My situation is as follows, I have a component which fetches a navigation param being passed to it from the previous screen using getParam - which works fine normally, I'm just struggling to successfully get a value in there using mock data. My code looks like this:
In my container I have this in the render method:
const tickets = navigation.getParam('tickets', null);
Then in my test I have the following:
const createTestProps = (testProps: Object, navProps: any = {}) =>
({
navigation: {
navigate: jest.fn(),
getParam: jest.fn(),
...navProps,
},
...testProps,
} as any);
let props = createTestProps(
{},
{
state: {
// Mock navigation params
params: {
tickets: [
{
cellNumber: '123456789',
ticketId: 'xxx',
},
{
cellNumber: '123456789',
ticketId: 'xxx',
},
],
},
},
}
);
const container = mount(
<MockedProvider mocks={mocks} addTypename={false}>
<ThemeProvider theme={theme}>
<TicketSummaryScreen {...props} />
</ThemeProvider>
</MockedProvider>
);
As you can see I've attempted to mock the actual navigation state, which I've checked against what's actually being used in the real component, and it's basically the same. The value for tickets is still undefined each time I run the test. I'm guessing it has to do with how I've mocked the getParam function.
Anyone have any ideas? Would be much appreciated!
I just successfully fixed this problem on my project. The only advantage that I had is that I had the render method being imported from a file I created. This is a great because all my tests can be fixed by just changing this file. I just had to merge some mocked props into the component that render was receiving.
Here's what it looked like before:
/myproject/jest/index.js
export { render } from 'react-native-testing-library'
After the fix:
import React from 'react'
import { render as jestRender } from 'react-native-testing-library'
const navigation = {
navigate: Function.prototype,
setParams: Function.prototype,
dispatch: Function.prototype,
getParam: (param, defaultValue) => {
return defaultValue
},
}
export function render(Component) {
const NewComponent = React.cloneElement(Component, { navigation })
return jestRender(NewComponent)
}
This setup is great! It just saved me many refactoring hours and probably will save me more in the future.
Maybe try returning the mock data from getParam
Try bellow example code.
const parameters = () => {
return "your value"
}
.....
navigation: {
getParam: parameters,
... navProps
},
... testProps
});
Give it a try
const navState = { params: { tickets: 'Ticket1', password: 'test#1234' } }
const navigation = {
getParam: (key, val) => navState?.params[key] ?? val,
}
here navState values will be params that you are passing.

How to unit test Next.js dynamic components?

The Next.js dynamic() HOC components aren't really straightforward to tests. I have 2 issues right now;
First jest is failing to compile dynamic imports properly (require.resolveWeak is not a function - seems to be added by next babel plugin)
Second I can't get good coverage of the modules logic; looks like it's simply not run when trying to render a dynamic component.
Let's assume we have a component like this (using a dynamic import):
import dynamic from 'next/dynamic';
const ReactSelectNoSSR = dynamic(() => import('../components/select'), {
loading: () => <Input />,
ssr: false
});
export default () => (
<>
<Header />
<ReactSelectNoSSR />
<Footer />
</>
);
The dynamic import support offered by Next.js does not expose a way to preload the dynamically imported components in Jest’s environment.
However, thanks to jest-next-dynamic, we can render the full component tree instead of the loading placeholder.
You'll need to add babel-plugin-dynamic-import-node to your .babelrc like so.
{
"plugins": ["babel-plugin-dynamic-import-node"]
}
Then, you can use preloadAll() to render the component instead of the loading placeholder.
import preloadAll from 'jest-next-dynamic';
import ReactSelect from './select';
beforeAll(async () => {
await preloadAll();
});
📝 Source
You can add the following to your Jest setup ex: setupTests.ts
jest.mock('next/dynamic', () => () => {
const DynamicComponent = () => null;
DynamicComponent.displayName = 'LoadableComponent';
DynamicComponent.preload = jest.fn();
return DynamicComponent;
});
The following will load the required component.
You can also use similar approach to load all components before hand.
jest.mock('next/dynamic', () => ({
__esModule: true,
default: (...props) => {
const dynamicModule = jest.requireActual('next/dynamic');
const dynamicActualComp = dynamicModule.default;
const RequiredComponent = dynamicActualComp(props[0]);
RequiredComponent.preload
? RequiredComponent.preload()
: RequiredComponent.render.preload();
return RequiredComponent;
},
}));
Although a hacky solution, what I did was to simply mock next/dynamic by extracting the import path and returning that import:
jest.mock('next/dynamic', () => ({
__esModule: true,
default: (...props) => {
const matchedPath = /(.)*(\'(.*)\')(.)*/.exec(props[0].toString());
if (matchedPath) return require(matchedPath[3]);
else return () => <></>;
},
}));
Here is an easy fix, which:
Doesn't require any 3rd party libraries
Still renders the dynamic component (e.g. snapshots will work)
SOLUTION:
Mock the next/dynamic module, by creating a mock file in __mocks__/next/dynamic.js Jest docs
Copy and paste the following code:
const dynamic = (func) => {
const functionString = func.toString()
const modulePath = functionString.match(/"(.*?)"/)[1]
const namedExport = functionString.match(/mod\.(.+?(?=\)))/)
const componentName = namedExport ? namedExport[1] : 'default'
return require(modulePath)[componentName]
}
export default dynamic
The above code assumes that you are using the dynamic() function for default and named exports as documented in the Next.js docs. I.e.:
// default
const DynamicHeader = dynamic(() =>
import('../components/header')
)
// named export
const DynamicComponent = dynamic(() =>
import('../components/hello').then((mod) => mod.Hello)
)
That's it! Now dynamic imports should work in all your test suites :)
If you're having the first issue with Next8, you can mock the dynamic import with this:
jest.mock('next-server/dynamic', () => () => 'Dynamic');
For reference, see:
https://spectrum.chat/next-js/general/with-jest-and-dynamic-imports-broken~25905aad-901e-41d8-ab3e-9f97eeb51610?m=MTU1MzA5MTU2NjI0Nw==
https://github.com/zeit/next.js/issues/6187#issuecomment-467134205

Jest enzyme find giving error in container test

I have a react container:
export const SomeContainer = ({disabled, onClick}) => {
let someElement = <img src="/path">
return (
<CustomButton className="my-class" icon={someElement} onClick={onClick} disabled={disabled}>
);
};
const mapStateToProps = (state) => ({
disabled: state.stateSet1.disabled,
});
const mapDispatchToProps = (dispatch) => ({
onClick: () => { dispatch(someAction()); },
});
SomeContainer.propTypes = {
disabled: PropTypes.bool.isRequired,
onClick: PropTypes.func.isRequired,
};
export default connect(mapStateToProps, mapDispatchToProps)(SomeContainer);
I'm writing the test like this:
import { SomeContainer } from './SomeContainer'
describe('SomeContainer', () => {
const onClickMock = jest.fn();
// this test is passing
it('has my class', () => {
const initialProps = {
disabled: false,
onClick: onClickMock,
};
const someContainer = shallow(<SomeContainer {...initialProps} />);
expect(someContainer.hasClass('my-class')).toEqual(true);
});
// this test is failing
it('has image icon', () => {
const initialProps = {
disabled: false,
onClick: onClickMock,
};
const someContainer = shallow(<SomeContainer {...initialProps} />);
expect(someContainer.find('img')).toEqual(true);
});
});
The error is:
TypeError: val.entries is not a function
at printImmutableEntries (node_modules/pretty-format/build/plugins/immutable.js:45:5)
at Object.exports.serialize (node_modules/pretty-format/build/plugins/immutable.js:179:12)
at printPlugin (node_modules/pretty-format/build/index.js:245:10)
at printer (node_modules/pretty-format/build/index.js:290:12)
at keys.map.key (node_modules/pretty-format/build/plugins/lib/markup.js:30:19)
at Array.map (native)
at exports.printProps (node_modules/pretty-format/build/plugins/lib/markup.js:28:3)
at Object.exports.serialize (node_modules/pretty-format/build/plugins/react_element.js:57:24)
at printPlugin (node_modules/pretty-format/build/index.js:245:10)
at printer (node_modules/pretty-format/build/index.js:290:12)
at keys.map.key (node_modules/pretty-format/build/plugins/lib/markup.js:30:19)
at Array.map (native)
at exports.printProps (node_modules/pretty-format/build/plugins/lib/markup.js:28:3)
at Object.exports.serialize (node_modules/pretty-format/build/plugins/react_element.js:57:24)
at printPlugin (node_modules/pretty-format/build/index.js:245:10)
at printer (node_modules/pretty-format/build/index.js:290:12)
at printObjectProperties (node_modules/pretty-format/build/collections.js:180:21)
at printComplexValue (node_modules/pretty-format/build/index.js:232:42)
at printer (node_modules/pretty-format/build/index.js:302:10)
at printObjectProperties (node_modules/pretty-format/build/collections.js:180:21)
at printComplexValue (node_modules/pretty-format/build/index.js:232:42)
at prettyFormat (node_modules/pretty-format/build/index.js:446:10)
at pass (node_modules/expect/build/matchers.js:439:50)
at message (node_modules/expect/build/index.js:107:16)
at Object.throwingMatcher [as toEqual] (node_modules/expect/build/index.js:215:23)
at Object.<anonymous> (tests/jest/containers/SomeButton.test.js:63:38)
at process._tickCallback (internal/process/next_tick.js:103:7)
Am I testing this wrong? I'm not able to figure out with this error whats wrong with the test. I have similar tests for react components (which does not use mapDispatchToProps but are using connect to get the state) and those are passing (I'm using mount there). I also tries wrapping it in the provider in test but get the same error.
If I use mount instead of shallow I get this error:
TypeError: 'Symbol(Symbol.toPrimitive)' returned for property 'Symbol(Symbol.toPrimitive)' of object '[object Object]' is not a function
If you need to test the existence of child components (that are deeper than direct children of your container component) you'll need to use render, otherwise the children won't ever get rendered.
Also, a common pattern for testing redux-connected components, where the test does not rely on the redux functionality, is to export a second, not default unwrapped component from your component file. For example:
export unwrappedSomeContainer = SomeContainer;
export default connect(mapStateToProps, mapDispatchToProps)(SomeContainer);
Then, in your test, import the unwrapped version:
import { unwrappedSomeContainer } from './SomeContainer';
// In test
const someContainer = shallow(<unwrappedSomeContainer {...initialProps} />);
This way, when doing a shallow render, you're not being blocked by the higher-order component that redux attaches as a parent to the component that you're trying to test.

Resources