Mocking compound components with Jest - reactjs

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

Related

how to test that props are passed to child component with react testing library and jest? [duplicate]

My component looks something like this: (It has more functionality as well as columns, but I have not included that to make the example simpler)
const WeatherReport: FunctionComponent<Props> = ({ cityWeatherCollection, loading, rerender }) => {
/* some use effects skipped */
/* some event handlers skipped */
const columns = React.useMemo(() => [
{
header: 'City',
cell: ({ name, title }: EnhancedCityWeather) => <Link to={`/${name}`} className="city">{title}</Link>
},
{
header: 'Temp',
cell: ({ temperature }: EnhancedCityWeather) => (
<div className="temperature">
<span className="celcius">{`${temperature}°C`}</span>
<span className="fahrenheit">{` (~${Math.round(temperature * (9 / 5)) + 32}°F)`}</span>
</div>
)
},
{
header: '',
cell: ({ isFavorite } : EnhancedCityWeather) => isFavorite && (
<HeartIcon
fill="#6d3fdf"
height={20}
width={20}
/>
),
},
], []);
return (
<Table columns={columns} items={sortedItems} loading={loading} />
);
};
Now, I wrote some tests like this:
jest.mock('../../../components/Table', () => ({
__esModule: true,
default: jest.fn(() => <div data-testid="Table" />),
}));
let cityWeatherCollection: EnhancedCityWeather[];
let loading: boolean;
let rerender: () => {};
beforeEach(() => {
cityWeatherCollection = [/*...some objects...*/];
loading = true;
rerender = jest.fn();
render(
<BrowserRouter>
<WeatherReport
cityWeatherCollection={cityWeatherCollection}
loading={loading}
rerender={rerender}
/>
</BrowserRouter>
);
});
it('renders a Table', () => {
expect(screen.queryByTestId('Table')).toBeInTheDocument();
});
it('passes loading prop to Table', () => {
expect(Table).toHaveBeenCalledWith(
expect.objectContaining({ loading }),
expect.anything(),
);
});
it('passes items prop to Table after sorting by isFavorite and then alphabetically', () => {
expect(Table).toHaveBeenCalledWith(
expect.objectContaining({
items: cityWeatherCollection.sort((item1, item2) => (
+item2.isFavorite - +item1.isFavorite
|| item1.name.localeCompare(item2.name)
)),
}),
expect.anything(),
);
});
If you check my component, it has a variable called columns. I am assigning that variable to Table component.
I think, I should test that columns are being passed as props to the Table component. Am I thinking right? If so, can you please tell me how can I write a test case for that?
Also, it will be helpful if you can suggest me how can i test each cell declared inside columns property.
It is not recommended to test implementation details, such as component props, with React Testing Library. Instead you should be asserting on the screen content.
Recommended
expect(await screen.findByText('some city')).toBeInTheDocument();
expect(screen.queryByText('filtered out city')).not.toBeInTheDocument();
Not Recommended
If you want to test props anyways, you can try the sample code below. Source
import Table from './Table'
jest.mock('./Table', () => jest.fn(() => null))
// ... in your test
expect(Table).toHaveBeenCalledWith(props, context)
You might consider this approach mainly on the two following scenarios.
You already tried the recommended approach but you noticed the component is:
using legacy code and because of that it makes testing very hard. Refactoring the component would also take too long or be too risky.
is very slow and it drastically increases the testing time. The component is also already tested somewhere else.
have a look at a very similar question here
You can use the props() method, doing something like this:
expect(Table.props().propYouWantToCheck).toBeFalsy();
Just doing your component.props() then the prop you want, you can make any assert with it.

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

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

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.

how to change jest mock function return value in each test?

I have a mock module like this in my component test file
jest.mock('../../../magic/index', () => ({
navigationEnabled: () => true,
guidanceEnabled: () => true
}));
these functions will be called in render function of my component to hide and show some specific feature.
I want to take a snapshot on different combinations of the return value of those mock functions.
for suppose I have a test case like this
it('RowListItem should not render navigation and guidance options', () => {
const wrapper = shallow(
<RowListItem type="regularList" {...props} />
);
expect(enzymeToJson(wrapper)).toMatchSnapshot();
});
to run this test case I want to change the mock module functions return values to false like this dynamically
jest.mock('../../../magic/index', () => ({
navigationEnabled: () => false,
guidanceEnabled: () => false
}));
because i am importing RowListItem component already once so my mock module wont re import again. so it wont change. how can i solve this ?
You can mock the module so it returns spies and import it into your test:
import {navigationEnabled, guidanceEnabled} from '../../../magic/index'
jest.mock('../../../magic/index', () => ({
navigationEnabled: jest.fn(),
guidanceEnabled: jest.fn()
}));
Then later on you can change the actual implementation using mockImplementation
navigationEnabled.mockImplementation(()=> true)
//or
navigationEnabled.mockReturnValueOnce(true);
and in the next test
navigationEnabled.mockImplementation(()=> false)
//or
navigationEnabled.mockReturnValueOnce(false);
what you want to do is
import { navigationEnabled, guidanceEnabled } from '../../../magic/index';
jest.mock('../../../magic/index', () => ({
navigationEnabled: jest.fn(),
guidanceEnabled: jest.fn()
}));
describe('test suite', () => {
it('every test', () => {
navigationEnabled.mockReturnValueOnce(value);
guidanceEnabled.mockReturnValueOnce(value);
});
});
you can look more about these functions here =>https://facebook.github.io/jest/docs/mock-functions.html#mock-return-values
I had a hard time getting the accepted answers to work - my equivalents of navigationEnabled and guidanceEnabled were undefined when I tried to call mockReturnValueOnce on them.
Here's what I had to do:
In ../../../magic/__mocks__/index.js:
export const navigationEnabled = jest.fn();
export const guidanceEnabled = jest.fn();
in my index.test.js file:
jest.mock('../../../magic/index');
import { navigationEnabled, guidanceEnabled } from '../../../magic/index';
import { functionThatReturnsValueOfNavigationEnabled } from 'moduleToTest';
it('is able to mock', () => {
navigationEnabled.mockReturnValueOnce(true);
guidanceEnabled.mockReturnValueOnce(true);
expect(functionThatReturnsValueOfNavigationEnabled()).toBe(true);
});

Resources