How to mock an image and path in JEST + webpack5 + React? - reactjs

I am trying to test a simple image component which is as follows:
const ImageComponent = (props) => {
const fullPath = require(`assets/images/${props.src}`);
return <img {...props} src={fullPath} alt={props.src} />;
};
and trying to test gives Error:
Error: Cannot find module 'assets/images/BigFakeImage' from 'test/components/common/ImageComponent.test.js'
test excerpt is as follows :
const props = {
src: 'BigFakeImage',
className: 'class-name-image',
alt: 'big picture'
};
const {result} = renderHook(() => ImageComponent(props), {
wrapper: ({children}) => (
<Provider store={store}>{children}</Provider>
)
});
return result.current;
};
it('should render <img/> with defined props', () => {
jest.mock('assets/images/BigFakeImage');
const wrapper = setup();
expect(wrapper).toMatchSnapshot();
});
I also tried the "moduleNameMapper" settings as specified here : https://jestjs.io/docs/webpack
But it causes the test suite to fail.
what can be done to mock the fake image and path and for the test to pass?
TIA.

Related

React test not working with current Jest config, Gatsby and gatsby-theme-i18n

I am fairly new to testing with Gatsby and Jest and I am encountering issues with my current setup.
I am trying to build a site with Gatsby and I would like for CI purposes to run unit tests with Jest and react-testing-library.
I initially was able to run a simple test:
describe('<Header>', () => {
describe('mounts', () => {
test('component mounts', () => {
const { container } = render(
<Header />
);
expect(container).toBeInTheDocument();
});
});
describe('click', () => {
const { container } = render(<Header />);
expect(screen.getByText('Light mode ☀')).toBeInTheDocument();
fireEvent(
getByText(container, 'Light mode ☀'),
new MouseEvent('click', {
bubbles: true,
cancelable: true,
}),
);
expect(screen.getByText('Dark mode ☾')).toBeInTheDocument();
});
where my render is a customized one:
const AllTheProviders = ({ children }) => {
const [dark, setDark] = useState(true);
return (
<ThemeContext.Provider
value={{
dark,
toggleDark: () => setDark(!dark),
}}
>
{children}
</ThemeContext.Provider>
);
};
const customRender = (ui, options) =>
render(ui, { wrapper: AllTheProviders, ...options });
// re-export everything
export * from '#testing-library/react';
// override render method
export { customRender as render };
and the Header a simple component that consumes the context and toggles the theme from dark to light.
I since then added gatsby-theme-i18n to handle i18n and tweaked my jest config to add:
transformIgnorePatterns: ['node_modules/(?!(gatsby-theme-i18n)/)']
The Header is now wrapped in a Layout component that provides the locale and location (with the Help for Location and #reach/router that comes with Gatsby) so I can have a language toggle in the header
In the Layout:
import { Location } from '#reach/router';
[...]
<Location>
{(location) => (
<Header
siteTitle={data.site.siteMetadata.title}
location={location}
locale={locale}
/>
)}
</Location>
The Header makes use of LocalizedLink from gatsby-theme-i18n` to prefix the right locale
<li key={langKey}>
<LocalizedLink key={langKey} to={to} language={langKey}>
{langKey}
</LocalizedLink>
</li>
I also changed the test to:
describe('mounts', () => {
test('component mounts', () => {
const { container } = render(
<Location>
{(location) => (
<Header siteTitle="site title" location={location} locale="en" />
)}
</Location>,
);
expect(container).toBeInTheDocument();
});
});
However, I get an error when trying to run the test:
TypeError: Cannot read property 'themeI18N' of undefined
21 |
22 | const customRender = (ui, options) =>
> 23 | render(ui, { wrapper: AllTheProviders, ...options });
| ^
specifically coming from at useLocalization (node_modules/gatsby-theme-i18n/src/hooks/use-localization.js:9:7)
where the Gatsby theme provides the i18n info through a graphQL query:
const {
themeI18N: { defaultLang, config },
} = useStaticQuery(graphql`
{
themeI18N {
defaultLang
config {
code
hrefLang
dateFormat
langDir
localName
name
}
}
}
`)
I understand that Jest does not 'understand' what to do with the i18n passed through the gatsby-theme-i18n via the Layout and if I switch my LocalizedLink to a normal Gatsby Link, the test passes (and I can console.log the container to see it's indeed a React element with the right info).
I tried a few things I saw online like using const { useLocalization } = require('gatsby-theme-i18n'); but to no avail.
Any idea how to handle this?
Should I mock the utils from gatsby-theme-i18n like other elements from Gatsby are mocked (Link etc.) following the docs?
EDIT
Thanks to the author of the library, LekoArts, and a friend a mine, I managed to make it work :)
If somebody has the same issue, you need to mock the response from the useStaticQuery of the useLocalization hook.
I have a mock file that does it:
// Libs
const React = require('react');
const gatsby = jest.requireActual('gatsby');
// Utils
const siteMetaData = require('../src/utils/siteMetaData');
module.exports = {
...gatsby,
graphql: jest.fn(),
Link: jest.fn().mockImplementation(
// these props are invalid for an `a` tag
({
activeClassName,
activeStyle,
getProps,
innerRef,
partiallyActive,
ref,
replace,
to,
...rest
}) =>
React.createElement('a', {
...rest,
href: to,
}),
),
StaticQuery: jest.fn(),
useStaticQuery: jest.fn().mockImplementation(() => ({
site: {
siteMetadata: {
...siteMetaData,
},
},
themeI18N: {
defaultLang: 'en',
config: {
code: 'en',
hrefLang: 'en-CA',
name: 'English',
localName: 'English',
langDir: 'ltr',
dateFormat: 'MM/DD/YYYY',
},
},
})),
};
and my test:
// Libs
import React from 'react';
// Utils
import { Location } from '#reach/router';
import { render, fireEvent, getByText, screen } from './utils/test-utils';
// Component
import Header from '../header';
describe('<Header>', () => {
describe('mounts', () => {
test('component mounts', () => {
const { container } = render(
<Location>
{(location) => (
<Header siteTitle="site title" location={location} locale="en" />
)}
</Location>,
);
expect(container).toBeInTheDocument();
});
});
describe('click', () => {
test('toggle language', () => {
const { container } = render(
<Location>
{(location) => (
<Header siteTitle="site title" location={location} locale="en" />
)}
</Location>,
);
expect(screen.getByText('Light mode ☀')).toBeInTheDocument();
fireEvent(
getByText(container, 'Light mode ☀'),
new MouseEvent('click', {
bubbles: true,
cancelable: true,
}),
);
expect(screen.getByText('Dark mode ☾')).toBeInTheDocument();
});
});
});
You can find more information here

Using React Jest Enzyme - How to pass a constructor object to my component with Shallow?

using react with typescript, jest and enzyme.
trying to unitest a functional component, which has an object
as parameter:
const JobListItem: React.FC<{ job: IJob }> = ({ job }) => {
in my test - this is how i try to setup the component:
const setup = (props={ }) => {
const wrapper = shallow(<JobListItem<{ job: IJob }> {...props} />)
return wrapper;
}
with simpler components (which don't have the IJob - just props)
the setup works with this syntax:
const setup = (props={}) => {
const wrapper = shallow(<JobList {...props} />)
return wrapper;
}
what am i missing? how can i pass the "IJob" to the shallow?
the error i'm receiving is:
(property) job: IJob
expected 0 type arguments but got 1
If you wish to provide generics typings to your component wrapper, you can simply do this:
describe('Test <JobListItem /> component', () => {
it('sample test', () => {
const props = {
job: .....,
}
const JobListItemComponent = <JobListItem {...props} />
const wrapper = shallow<IJob>(JobListItemComponent);
});
});
This will provide typechecking, and ensure that the wrapper will only take in props of type IJob.
Or, if you just want to make the changes to your code, do not supply the props with a default empty object, because it is not of IJob type.
const setup = (props: IJob) => {
const JobListItemComponent = <JobListItem {...props} />
const wrapper = shallow<IJob>(JobListItemComponent);
return wrapper;
}

Jest - Testing modals in React gives error

I am using react-test-renderer with Jest to test react components. But if I test a react-mui modal dialog like this:
describe('Dashboard', function () {
let dashboard;
beforeEach(async () => {
testRenderer = TestRenderer.create(<MemoryRouter><Route component={Dashboard} /></MemoryRouter>);
dashboard = testRenderer.root.findByType(Dashboard);
await waitForExpect(() => expect(dashboard.instance.state.hasLoaded).toBeTruthy());
});
it('opens dialog on clicking the new class', async () => {
const button = testRenderer.root.findByType(Button);
expect(dashboard.instance.state.showDialog).toBeFalsy();
button.props.onClick();
expect(dashboard.instance.state.showDialog).toBeTruthy();
});
});
But, then I get an error:
Error: Failed: "Error: Uncaught 'Warning: An invalid container has
been provided. This may indicate that another renderer is being used
in addition to the test renderer. (For example, ReactDOM.createPortal
inside of a ReactTestRenderer tree.) This is not supported.%s'
How should I test then react portals to make this test work?
Try putting this in your tests:
beforeAll(() => {
ReactDOM.createPortal = jest.fn((element, node) => {
return element
})
});
Based on Oliver's answer, but for TypeScript users:
describe("Tests", () => {
const oldCreatePortal = ReactDOM.createPortal;
beforeAll(() => {
ReactDOM.createPortal = (node: ReactNode): ReactPortal =>
node as ReactPortal;
});
afterAll(() => {
ReactDOM.createPortal = oldCreatePortal;
});
});
For me, the existing solutions don't address the root cause.
I needed to add jest mocks for all the sub-components in the component I was testing.
For example, consider this JSX that I want to test:
import { CustomTextInput } from 'components/CustomTextInput';
import { CustomButton } from 'components/CustomButton';
return (
<>
<CustomTextInput />
<CustomButton />
</>
)
I need to add mocks for CustomTextInput and CustomButton in my test file like this:
jest.mock(
'components/CustomTextInput',
() => ({ default: 'mock-CustomTextInput' }),
);
jest.mock(
'components/CustomButton',
() => ({ default: 'mock-CustomButton' }),
);

wrapper.find is not a function

I am trying test a component in React w/ TypeScript using Jest and Enzyme.
My test is as follows:
import * as React from 'react';
import { shallow } from 'enzyme';
import * as sinon from 'sinon';
import { ButtonGroup } from '../../../src/components';
describe('.ButtonGroup', () => {
it('should render', () => {
const { wrapper } = setup({});
expect(wrapper.exists()).toBe(true);
});
it('should call the rightHandler handler on click', () => {
const onClickHandler = sinon.spy();
const wrapper = setup({ rightHandler: onClickHandler });
wrapper.find('a').simulate('click');
expect(onClickHandler).toHaveBeenCalled();
});
});
const setup = propOverrides => {
const props = Object.assign({ leftBtn: MOCK_LEFT, rightBtn: MOCK_RIGHT }, propOverrides);
const wrapper = shallow(<ButtonGroup {...props} />);
return { wrapper };
};
const MOCK_LEFT = { type: 'reset', className: 'is-light', value: 'Reset' };
const MOCK_RIGHT = { type: 'button', className: 'is-primary', value: 'Search' };
However I am getting an error: TypeError: wrapper.find is not a function
The component I am testing looks like
import * as React from 'react';
const ButtonGroup = ({ leftBtn, rightBtn, leftHandler = null, rightHandler = null }) => (
<div className="field is-grouped is-grouped-right">
<p className="control">
<input type={leftBtn.type} className={'button ' + leftBtn.className} value={leftBtn.value} onClick={leftHandler} />
</p>
<p className="control">
<input type={rightBtn.type} className={'button ' + rightBtn.className} value={rightBtn.value} onClick={rightHandler} />
</p>
</div>
);
export default ButtonGroup;
I would like to essentially asset that on click, the action I expect is called.
Try
const setup = propOverrides => {
const props = Object.assign({ leftBtn: MOCK_LEFT, rightBtn: MOCK_RIGHT }, propOverrides);
const wrapper = shallow(<ButtonGroup {...props} />);
return wrapper;
};
Doing return { wrapper } is the same as return { wrapper: wrapper } so it's not your DOM element anymore, but an object with a property wrapper containing your DOM element.
This is a small bin to illustrate the issue.

testing onClick with react-navigation

I'm using Jest with Enzyme, and I have this component which includes a navigate method call:
export class LeadList extends React.Component {
render() {
const { navigate } = this.props.navigation;
return (
<List>
{this.props.data.allLeads.map((lead, i) => {
return (
<ListItem
key={i}
onPress={() =>
navigate('Details', lead.id)
}
/>
// ...
</ListItem>
)})}
</List>
);
}
}
I'm trying to test that it gets called properly, so I threw this together:
const testProps = props => ({
data: {
allLeads: [
{id: 1, name: 'John Doe'},
{id: 2, name: 'Jane Doe'}
],
loading: false,
},
navigation: jest.fn((options, callback) => callback('Details', 1)),
...props,
})
describe('interactions', () => {
let props
let wrapper
beforeEach(() => {
props = testProps()
wrapper = shallow(<LeadList {...props} />)
})
describe('clicking a lead', () => {
beforeEach(() => {
wrapper.find(ListItem).first().prop('onPress')
})
it('should call the navigation callback', () => {
expect(props.navigation).toHaveBeenCalledTimes(1)
})
})
})
Output is:
Expected mock function to have been called one time, but it was called zero times.
What's the right way to handle this? Do I need to use a spy?
EDIT:
I'm getting the same when I change it like so:
const testProps = props => ({
// ...
navigation: {navigate: jest.fn()},
...props,
})
it('should call the navigation callback', () => {
expect(props.navigation.navigate).toHaveBeenCalledTimes(1)
})
Output:
expect(jest.fn()).toHaveBeenCalledTimes(1)
Expected mock function to have been called one time, but it was called zero times.
at Object.<anonymous> (__tests__/LeadList-test.js:48:35)
at tryCallTwo (node_modules/promise/lib/core.js:45:5)
at doResolve (node_modules/promise/lib/core.js:200:13)
at new Promise (node_modules/promise/lib/core.js:66:3)
at Promise.resolve.then.el (node_modules/p-map/index.js:46:16)
at tryCallOne (node_modules/promise/lib/core.js:37:12)
at node_modules/promise/lib/core.js:123:15
You will need a spy to test this. Here is an example test for finding the ForgotPassword button on a LoginScreen and testing that it navigates to the correct screen.
test('Press Forgot Password Button', () => {
const spy = jest.spyOn(navigation, 'navigate')
const wrapper = shallow(
<LoginScreen
navigation={navigation}
error={{}}
onLogin={jest.fn()}
/>,
)
const forgotButton = wrapper.find('Button').at(0)
forgotButton.props().onPress()
expect(spy).toBeCalledWith('ForgotPassword')
})
The prop navigation that is passed to the component is not a function. It's an object that contains a function called navigate.
Ironically, that's exactly what you're using in your component code:
const { navigate } = this.props.navigation;
And so, you'll have to change the navigation prop that you're passing from the test to be:
navigation: {navigate: jest.fn()}
and then in your test:
expect(props.navigation.navigate).toHaveBeenCalledTimes(1)
Edit:
In order to actually get the function to be called, you'll have to simulate a press. Right now the code finds the onPress function, but doesn't invoke it.
To do this you can replace
wrapper.find(ListItem).first().prop('onPress')
with
wrapper.find(ListItem).first().props().onPress()

Resources