Why is my mock store not working on Jest/Enzyme? - javascript

According to the Jest test logs, I am missing coverage on line 67 which is present on the chunk of code below
export default compose(
connect(store => ({ // LINE 67
accountGuid: store.global.accountGuid,
shipmentsCSV: store.shipments.shipmentsCSV,
})),
translate(),
)(DownloadCSVItem);
And this is the test I have for that component:
import React from 'react';
import { mount } from 'enzyme';
import DownloadCSVItem from '../../DownloadCSVItem';
import createMockStore from '../../../../utils/createMockStore';
describe('DownloadCSVItem component', () => {
let props;
beforeEach(() => {
props = {
t: k => k,
accountGuid: 'abcd-1234',
shipmentsCSV: {
itemsCount: 2,
shipments: [
{
Courier: 'Hand Delivery',
Note: 'Testing the data transfer request system. ',
},
{
Courier: null,
Note: null,
},
],
},
};
});
it('renders DownloadCSVItem and check if class exists', () => {
const wrapper = mount(
<DownloadCSVItem.WrappedComponent {...props}
store={createMockStore({
global: { accountGuid: props.accountGuid },
shipments: {
totalCount: props.shipmentsCSV.itemsCount,
shipments: props.shipmentsCSV.shipments,
},
})}
/>,
);
expect(wrapper.find('button')).toHaveLength(1);
});
});
I wrote that test and ran the command to test it and it still says coverage on line 67 is missing.
What should I do in this case?

Related

How to implement a unit test of a component whcih uses a custom hook

I'm having a component that includes 2 switches of Material UI and I want to build a unit test of it.
The component uses data from an API to set the switches off/on default and also has the possibility for the user to click and set it on/off
The data comes and it is set by a custom hook which probably needs to be mocked but I have difficulties doing it having errors
The custom hook is this one
Custom Hook
The goal is to test 4 scenarios
The data comes in that way making the switches off
The data comes in that way making the switches on
The user clicks event to turn on the switches
The user clicks event to turn off the switches
I started by number 1 but don't know how to make it work and this is the unit test I started doing
/**
* #jest-environment jsdom
*/
import { useConfiguration } from '#lib/hooks/useConfiguration';
import { render, screen } from 'test/app-test-utils';
import StudyConfiguration from './StudyConfiguration';
jest.mock('#lib/hooks/useConfiguration');
test.only('renders with SMS messaging and Email replay - all switches off disable', async () => {
jest
.spyOn({ useConfiguration }, 'useConfiguration')
.mockImplementationOnce(() => false)
.mockImplementationOnce(() => {value: 'noreply#test.com'})
.mockReturnValue([
{
scope: 'GLOBAL',
value: 'global#test.com',
},
{ scope: 'DEFAULT', value: 'hello#test.com' },
{ scope: 'STUDY', value: 'noreply#test.com' },
]);
const studyId = { studyId: 'study-1' };
render(<StudyConfiguration studyId={studyId} />);
const replyEmail = screen.getByTestId('email-reply');
const smsMessaging = screen.getByTestId('sms-enable');
// This throws an error if text not matching expect is no need it
screen.getByText(
/allow candidates to reply to emails \(send from global#test\.com instead of noreply#test\.com\)/i,
);
screen.getByText(/sms messaging/i);
expect(replyEmail.querySelector('input[type=checkbox]')).not.toBeChecked();
expect(smsMessaging.querySelector('input[type=checkbox]')).not.toBeChecked();
});
This gave the following errors inside the custom hook and I don't know the right way of mocking it
TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))
7 | const intl = useIntl();
8 |
> 9 | const [emailSettings, setSenderEmail, unsetSenderEmail] = useConfiguration({
| ^
10 | name: 'messaging.email.sender.address',
11 | scope: { studyId },
12 | });
The component
import { useConfiguration } from '#lib/hooks/useConfiguration';
import { FormControlLabel, FormGroup, Switch, Typography } from '#mui/material';
import { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
const StudyConfiguration = ({ studyId }) => {
const intl = useIntl();
const [emailSettings, setSenderEmail, unsetSenderEmail] = useConfiguration({
name: 'messaging.email.sender.address',
scope: { studyId },
});
const [smsSettings, setSmsSettings, unsetSmsSettings] = useConfiguration({
name: 'messaging.recruitment.sms.enable',
scope: { studyId },
defaultValue: false,
});
const [studyConfOverride, setStudyConfOverride] = useState(
emailSettings?.overrideChain,
);
const [replayEmailAddress, setReplayEmailAddress] = useState(
emailSettings?.value,
);
const [isSmsEnabled, setIsSmsEnabled] = useState(smsSettings?.value);
useEffect(() => {
if (studyConfOverride.length !== emailSettings?.overrideChain.length) {
setStudyConfOverride(emailSettings?.overrideChain);
}
}, [emailSettings?.overrideChain]); // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
if (replayEmailAddress !== emailSettings?.value) {
setReplayEmailAddress(emailSettings?.value);
}
}, [emailSettings?.value]); // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
if (isSmsEnabled !== smsSettings?.value) {
setIsSmsEnabled(smsSettings?.value);
}
}, [smsSettings?.value]); // eslint-disable-line react-hooks/exhaustive-deps
// Building the default reply email based on 'SCOPE'
// !TODO: study overrides sort in study service (TBD)
let defaultEmail;
if (studyConfOverride?.find(o => o.scope === 'GLOBAL')) {
const { value } = studyConfOverride.find(o => o.scope === 'GLOBAL');
defaultEmail = value;
} else if (studyConfOverride?.find(o => o.scope === 'DEFAULT')) {
const { value } = studyConfOverride.find(o => o.scope === 'DEFAULT');
defaultEmail = value;
}
// Extracting the email domain from default email and used to make a 'noreply#domain.xxx'
const emailDomain = defaultEmail?.substring(defaultEmail.indexOf('#'));
const noReplyEmail = `noreply${emailDomain}`;
const handleReplyEmailChange = async event => {
setReplayEmailAddress(event.target.checked ? defaultEmail : noReplyEmail);
event.target.checked
? await unsetSenderEmail()
: await setSenderEmail(noReplyEmail);
};
const handleSmsConf = async event => {
setIsSmsEnabled(event.target.checked);
event.target.checked
? await unsetSmsSettings()
: await setSmsSettings('false');
};
const isEmailEnabled = replayEmailAddress === defaultEmail;
return (
<FormGroup>
<FormControlLabel
control={
<Switch
data-testid="email-reply"
checked={isEmailEnabled}
onChange={handleReplyEmailChange}
/>
}
label={
<Typography color="textPrimary">
{intl.formatMessage(
{
defaultMessage:
'Allow candidates to reply to emails (send from {replyEmailTxt} instead of {noReplyTxt})',
},
{ replyEmailTxt: defaultEmail, noReplyTxt: noReplyEmail },
)}
</Typography>
}
/>
<FormControlLabel
control={
<Switch
data-testid="sms-enable"
checked={isSmsEnabled}
onChange={handleSmsConf}
/>
}
label={
<Typography color="textPrimary">
{intl.formatMessage({
defaultMessage: `SMS messaging`,
})}
</Typography>
}
/>
</FormGroup>
);
};
export default StudyConfiguration;
From the following section, we are calling the custom hook
const [emailSettings, setSenderEmail, unsetSenderEmail] = useConfiguration({
name: 'messaging.email.sender.address',
scope: { studyId },
});
const [smsSettings, setSmsSettings, unsetSmsSettings] = useConfiguration({
name: 'messaging.recruitment.sms.enable',
scope: { studyId },
defaultValue: false,
});
I don't know how to mock that and if we console log the first values this is what we have
Email settings:: {
"value": "noreply#test.com",
"overrideChain": [
{
"__typename": "ConfigurationOverrideSlab",
"value": "noreply#test.com",
"scope": "STUDY"
},
{
"__typename": "ConfigurationOverrideSlab",
"value": "global#test.com",
"scope": "GLOBAL"
},
{
"__typename": "ConfigurationOverrideSlab",
"value": "hello#app.trialbee.com",
"scope": "DEFAULT"
}
]
}
I should be able to mock the email settings to show the resulting OBJ and check for that the switches are off.
The other 2 values are functions
setSenderEmail:: ƒ (value) {
return setStudyConfiguration({
variables: {
input: {
name: name,
scope: scope,
value: value
}
}
});
}
unSetSenderEmail:: ƒ () {
return unsetStudyConfiguration({
variables: {
input: _objectSpread({}, input)
}
});
}

"Missing config for event" using #xstate/test

I'm testing a state machine using model-based testing using #xstate/test and #testing-library/react.
Basically, I'm testing this machine:
const itemDamagedMachine = createMachine({
initial: 'newModal',
context: {
productScan: '',
binScan: '',
},
states: {
newModal: {
initial: 'scanDamagedItem',
states: {
scanDamagedItem: {},
scanDamagedBin: {},
declareItemDamaged: {},
},
},
closed: {},
},
on: {
UPDATE_PRODUCT_SCAN: {
actions: assign({
productScan: 123456,
}),
},
VALIDATE: {
target: 'newModal.scanDamagedBin',
},
UNREADABLE: {
target: 'newModal.scanDamagedBin',
},
CANCEL: {
target: 'closed',
},
UPDATE_DAMAGED_BIN_SCAN: {
actions: assign({
binScan: 'PB_DAMAGED',
}),
},
},
});
I'm then configuring the model, and testing it using const testPlans = itemDamagedModel.getSimplePathPlans();.
Everything seems to run smoothly with about 200 passing tests, but I'm having a few issues:
For each of my test and each of my event, I'm getting a warning Missing config for event "VALIDATE". I don't understand what it's supposed to mean.
All of my tests are validated even if I make typos on purpose in my model event. Sometimes the number of tests is reduced, but I would have hoped to see a few warnings when the model doesn't find a particular input or button.
The tests are all passing, even if I'm passing an empty div as my xstate/test rendered component.
I do not get the idea, but I have tested a component as follow:
First I have my machine:
import { createMachine, sendParent } from 'xstate';
export const machineDefinition = {
id: 'checkbox',
initial: 'unchecked',
states: {
unchecked: {
on: {
TOGGLE: [
{
actions: [ 'sendParent' ],
target: 'checked',
},
],
},
},
checked: {
on: {
TOGGLE: [
{
actions: [ 'sendParent' ],
target: 'unchecked',
},
],
},
},
},
};
const machineOptions = {
actions: {
sendParent: sendParent((context, event) => event.data),
},
};
export default createMachine(machineDefinition, machineOptions);
Second, I have extended the render method of testing-library
import React from 'react'
import HelmetProvider from 'react-navi-helmet-async'
import SpinnerProvider from '#atoms/GlobalSpinner'
import AlertProvider from '#molecules/GlobalAlert'
import InternationalizationProvider from '#internationalization/InternationalizationProvider'
import { render as originalRender } from '#testing-library/react'
const render = (ui, { locale = 'es', ...renderOptions } = {}) => {
const Wrapper = ({ children }) => {
return (
<InternationalizationProvider>
<AlertProvider>
<SpinnerProvider>
<HelmetProvider>
{children}
</HelmetProvider>
</SpinnerProvider>
</AlertProvider>
</InternationalizationProvider>
)
}
return originalRender(ui, { wrapper: Wrapper, ...renderOptions })
}
export * from '#testing-library/react'
export { render }
Finally, I have created the test
import React from 'react';
import { produce } from 'immer';
import { machineDefinition } from '#stateMachines/atoms/checkbox';
import { createMachine } from 'xstate';
import { createModel } from '#xstate/test';
import { render, cleanup, fireEvent } from '#root/jest.utils';
import Checkbox from '#atoms/Checkbox';
const getMachineDefinitionWithTests = () => produce(machineDefinition, (draft) => {
draft.states.unchecked.meta = {
test: ({ getByTestId }) => {
expect(getByTestId('checkbox-child-3')).toHaveClass('w-8 h-4 rounded-md duration-500 bg-dark-300 dark:bg-accent-100');
},
};
draft.states.checked.meta = {
test: ({ getByTestId }) => {
expect(getByTestId('checkbox-child-3')).toHaveClass('w-8 h-4 rounded-md duration-500 bg-dark-300 dark:bg-accent-100');
expect(getByTestId('checkbox-child-3.1')).toHaveClass('bg-light-100 w-4 h-4 rounded-full duration-500 dark:transform dark:translate-x-full');
},
};
});
const getEvents = () => ({
TOGGLE: {
exec: ({ getByTestId }) => {
fireEvent.click(getByTestId('checkbox-container'));
},
cases: [ {} ],
},
});
describe('checkbox', () => {
const machine = createMachine(getMachineDefinitionWithTests(), {
actions: {
sendParent: () => {},
},
});
const machineModel = createModel(machine)
.withEvents(getEvents());
const testPlans = machineModel.getSimplePathPlans();
testPlans.forEach((plan) => {
describe(plan.description, () => {
afterEach(cleanup);
plan.paths.forEach((path) => {
it(path.description, () => {
const rendered = render(
<Checkbox
test
label='main.txt1'
data={{}}
machine={machine}
/>,
{ locale: 'en' },
);
return path.test(rendered);
});
});
});
});
describe('coverage', () => {
it('should have full coverage', () => {
machineModel.testCoverage();
});
});
});
I have created a react boilerplate which contains XState, there you can find the previous test

How to mock the same function called in different components with different returned values

I need to mock a function useAxios but this is called in two different components, and one of these components is used inside the other. This is my code:
import React, { useEffect, useState } from 'react'
import useAxios from 'axios-hooks'
import { Table, Space } from 'antd'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faEdit, faCalendar, faUserPlus, faTimes } from '#fortawesome/free-solid-svg-icons'
export const RemoveProjectButton = ({ project, updateProjects }) => {
const [, execute] = useAxios(
{
url: `${process.env.REACT_APP_API_URL}/projects/${project.key}/`,
method: 'delete'
},
{
manual: true
}
)
const removeProject = async (project) => {
await execute()
updateProjects(project)
}
return <a data-testid={`project-${project.key}`} onClick={() => { removeProject(project) }}><FontAwesomeIcon icon={faTimes} /></a>
}
export const Projects = () => {
const [projects, setProjects] = useState([])
const [{ data, loading, error }] = useAxios(
`${process.env.REACT_APP_API_URL}/projects/`
)
useEffect(() => {
setProjects(data)
}, [data])
useEffect(() => {}, [projects])
const updateProjects = (projectToDelete) => {
setProjects(() => projects.filter(project => project.key !== projectToDelete.key))
}
if (loading) return <p data-testid='loading'>Loading...</p>
if (error) return <p data-testid='error'>Error!</p>
const columns = [
{
title: 'Title',
dataIndex: 'title',
key: 'title',
render: title => <a>{title}</a>
},
{
title: 'Start Date',
dataIndex: 'startDate',
key: 'startDate'
},
{
title: 'Description',
dataIndex: 'description',
key: 'description',
render: description => `${description.substring(0, 50)}...`
},
{
title: 'Team',
dataIndex: 'team',
key: 'team'
},
{
title: 'Action',
key: 'action',
render: (text, record, index) => (
<Space size='middle'>
<FontAwesomeIcon icon={faEdit} />
<FontAwesomeIcon icon={faCalendar} />
<FontAwesomeIcon icon={faUserPlus} />
<RemoveProjectButton project={record} updateProjects={updateProjects} />
</Space>
)
}
]
return (
<Table
data-testid='project-table'
columns={columns}
dataSource={projects}
pagination={false}
/>
)
}
and this is my test:
import React from 'react'
import { render, cleanup, fireEvent } from '#testing-library/react'
import { Projects, RemoveProjectButton } from '../Projects'
import useAxios from 'axios-hooks'
jest.mock('axios-hooks')
const TABLE_TEST_ID = 'project-table'
const fakeData = [
{
key: 1,
title: 'Testing Project Alpha',
startDate: '2020-04-18',
description: 'This is just for testing',
team: 'A, B, C'
},
{
key: 2,
title: 'Testing Project Beta',
startDate: '2020-04-19',
description: 'This is just for testing too',
team: 'X, Y, Z'
}
]
describe('projects table', () => {
let projects
beforeEach(() => {
projects = JSON.parse(JSON.stringify(fakeData))
useAxios.mockReturnValue([{
data: projects,
loading: false,
error: null
}])
})
it('removes project when clicking on X button in row', async () => {
const { getByTestId, queryByTestId } = render(<Projects />)
const executeMock = jest.fn()
useAxios.mockReturnValue([{
data: projects,
loading: false,
error: null
}])
.mockReturnValue([{}, executeMock])
.mockReturnValue([{}, executeMock])
expect(getByTestId(TABLE_TEST_ID)).toHaveTextContent('Testing Project Alpha')
await fireEvent.click(getByTestId('project-1'))
expect(queryByTestId('project-1')).toBeNull()
expect(getByTestId(TABLE_TEST_ID)).not.toHaveTextContent('Testing Project Alpha')
})
})
However, I am getting the following error:
TypeError: execute is not a function
16 | )
17 | const removeProject = async (project) => {
> 18 | await execute()
| ^
19 | updateProjects(project)
20 | }
21 |
I understand that the problem is that I am not passing the correct mock for the component RemoveProjectButton. However, I have no idea how I can achieve this because useAxios is being called in different components, and it should have with different returns values. I have also tried using mockImplementationOnce, but it seems the component Projects gets rendered several times before rendering the component RemoveProjectButton, so I feel that I am like guessing the number of times I will have to use mockImplementationOnce.
Maybe just use mockImplementation then you can return different value according to the params.
useAxios.mockImplementation((...args) => {
if (match1(args)) {
return result1;
}
if (match2(args)) {
return result2;
}
return result3;
});
I took the approach suggested by #Mirone's answer, but I got another issue after the mocking issue was solved, so I will add my complete answer in case somebody else experiences the same:
it('removes project in table when clicking on X', async () => {
const executeMock = jest.fn()
useAxios.mockImplementation((...args) => {
// This mocking was inspired on Mirone's answer
switch (args.length) {
case 1:
return [{
data: projects,
loading: false,
error: null
}]
case 2:
return [{}, executeMock]
default: break
}
})
const { getByTestId, findByTestId } = render(<Projects />)
expect(getByTestId('project-table')).toHaveTextContent('Testing Project Alpha')
fireEvent.click(getByTestId('project-1'))
const table = await findByTestId('project-table')
expect(table).not.toHaveTextContent('Testing Project Alpha')
})
After the mocking issue was solved, I was getting an error:
Warning: An update to Projects inside a test was not wrapped in act(...).
This was solved by adding:
const table = await findByTestId('project-table')
The previous line was waiting until the element with data-testid="project-table" was re-rendered, but I got one more error:
MutationObserver is not a constructor
I found an issue on Github that provided the solution for this. Briefly speaking, the problem is that CRA is not using the latest version of jsdom, so the solution was so install jest-environment-jsdom-sixteen as follows:
yarn add jest-environment-jsdom-sixteen --dev
and also to set it up in package.json:
"scripts": {
...
"test": "react-scripts test --env=jest-environment-jsdom-sixteen",
...
}
I hope my answer helps someone else who might experience the same issues.

How to Jest test use of lodash.get in React component?

Error
TypeError: Cannot read property 'length' of undefined
My App component is making use of import get from 'lodash.get' https://lodash.com/docs/4.17.11#get
I'm using get inside my render function like so:
const getLabel = (listings, label) => {
const componentsMap = {
Deliveries: Delivery,
Dispensaries: Dispensary,
Doctors: Doctor
};
const DynamicIcon = componentsMap[label];
if (get(listings, 'listings').length) {
return (
<div key={label}>
<DynamicIcon fill={DARK_GRAY} /> <strong> {label} </strong>
</div>
);
}
return <div />;
};
App.test.js
import React from 'react'
import { shallow } from 'enzyme'
import toJson from 'enzyme-to-json'
import { AppJest } from './App'
import listingsMock from '../__test__/mocks/listings-mock.json';
// Mock the services.
const mockLocate = jest.fn();
const mockDisplayListing = jest.fn();
jest.mock('../actions', () => ({
locate: () => mockLocate(),
displayListing: () => mockDisplayListing()
}));
describe('<App /> component', () => {
describe('when rendering', () => {
const wrapper = shallow(<AppJest
listings={listingsMock}
locate={mockLocate}
displayListing={mockDisplayListing}
/>);
it('should render a component matching the snapshot', () => {
const tree = toJson(wrapper);
expect(tree).toMatchSnapshot();
expect(wrapper).toHaveLength(1);
});
});
});
I assumed it was because I wasn't mocking listings and passing it into the props of the shallow wrapper, but I added the mock.
listings-mock.json
{
"bottom_right": {
"latitude": 32.618865,
"longitude": -96.555516
},
"id": 1390,
"latitude": 32.78143692016602,
"listings": [
{
"avatar_image": {
"small_url": "https://images.weedmaps.com/deliveries/000/028/448/avatar/square_fill/1510581750-1507658638-Knox_Medical_Logo.png"
},
"city": "Dallas",
"distance": 2,
"id": 28448,
"license_type": "medical",
"name": "Knox Medical (Delivery Now Available)",
"online_ordering": {
"enabled_for_pickup": false,
"enabled_for_delivery": false
},
"package_level": "listing_plus",
"rating": 5,
"region_id": 1390,
"retailer_services": [
"delivery"
],
"slug": "knox-medical-dallas",
"state": "TX",
"static_map_url": "https://staticmap.weedmaps.com/static_map/13/32.7736/-96.795108/402/147/map.png",
"wmid": 459977538
}
],
"longitude": -96.7899169921875,
"name": "Dallas",
"region_path": "united-states/texas/dallas",
"slug": "dallas",
"top_left": {
"latitude": 33.016492,
"longitude": -96.999319
}
}
App.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import get from 'lodash.get';
import { locate, displayListing } from '../actions';
import Header from './header';
import Hero from './hero';
import Ripple from './partials/ripple';
import ListingCards from './listing_cards';
import Delivery from '../icons/delivery';
import Dispensary from '../icons/dispensary';
import Doctor from '../icons/doctor';
import { DARK_GRAY } from '../constants/colors';
import {
AppWrapper,
AppContent,
ListingGroups,
} from './styles';
const regionTypes = ['delivery', 'dispensary', 'doctor'];
const regionLabels = {
delivery: 'Deliveries',
dispensary: 'Dispensaries',
doctor: 'Doctors',
};
export class App extends Component {
constructor(props) {
super(props);
this.state = {
loadingTimer: 0,
isLocatingStarted: false,
geoCoords: null,
width: 0
};
this.locateMe = this.locateMe.bind(this);
this.gotoListing = this.gotoListing.bind(this);
}
componentDidMount() {
// Fetch geolocation ahead of time.
navigator.geolocation.getCurrentPosition(position =>
this.setState({ geoCoords: position.coords }));
this.updateWindowDimensions();
window.addEventListener("resize", this.updateWindowDimensions);
}
componentWillUnmount() {
window.removeEventListener("resize", this.updateWindowDimensions);
}
updateWindowDimensions = () => this.setState({ width: window.innerWidth });
locateMe() {
console.log('locateMe')
const { dispatch } = this.props;
const { geoCoords } = this.state;
if (navigator.geolocation && !geoCoords) {
navigator.geolocation.getCurrentPosition(position =>
dispatch(locate(position.coords)));
} else {
dispatch(locate(geoCoords));
}
this.setState({ isLocatingStarted: true });
};
gotoListing(listing) {
const { dispatch } = this.props;
dispatch(displayListing(listing));
const link = `/listing/${listing.wmid}`;
this.props.history.push(link);
}
render() {
const { isLocating, location, regions, error } = this.props;
const { isLocatingStarted, width } = this.state;
const { state_abv: state } = location !== null && location;
const isLoading = isLocatingStarted && isLocating;
const getLabel = (listings, label) => {
const componentsMap = {
Deliveries: Delivery,
Dispensaries: Dispensary,
Doctors: Doctor
};
const DynamicIcon = componentsMap[label];
if (get(listings, 'listings').length) {
return (
<div key={label}>
<DynamicIcon fill={DARK_GRAY} /> <strong> {label} </strong>
</div>
);
}
return <div />;
};
return (
<AppWrapper>
<Header history={this.props.history} />
<Hero
location={location}
isLocating={isLocating}
locateMe={this.locateMe}
/>
{ isLoading ? <Ripple /> :
<AppContent>
{error && <div> {error.message} </div>}
{regions && (
<React.Fragment>
{regionTypes.map(regionType => (
<ListingGroups key={regionType}>
<h2>
{getLabel(regions[regionType], regionLabels[regionType])}
</h2>
<ListingCards
listings={get(regions[regionType], 'listings')}
state={state}
isMobileSize={width < 769}
gotoListing={this.gotoListing}
/>
</ListingGroups>
))}
</React.Fragment>
)}
</AppContent>
}
</AppWrapper>
);
}
}
const mapStateToProps = state => state.location;
App.propTypes = {
isLocating: PropTypes.bool.isRequired,
location: PropTypes.object,
regions: PropTypes.object,
dispatch: PropTypes.any,
error: PropTypes.object,
};
App.defaultProps = {
isLocating: false,
location: {},
regions: {},
error: {},
};
export const AppJest = App
export default connect(mapStateToProps)(App);
Ah I needed to finish adding in all the props to the wrapper component:
import React from 'react'
import { shallow } from 'enzyme'
import toJson from 'enzyme-to-json'
import { AppJest } from './App'
import Header from './header';
import Hero from './hero';
import Ripple from './partials/ripple';
import listingsMock from '../__test__/mocks/listings-mock.json';
// Mock the services.
const mockLocate = jest.fn();
const mockDisplayListing = jest.fn();
const mockGeolocation = {
getCurrentPosition: jest.fn(),
watchPosition: jest.fn()
};
global.navigator.geolocation = mockGeolocation;
jest.mock('../actions', () => ({
locate: () => mockLocate(),
displayListing: () => mockDisplayListing()
}));
describe('<App /> component', () => {
describe('when rendering', () => {
const wrapper = shallow(<AppJest
navigator={mockGeolocation}
isLocating={false}
location={null}
regions={null}
dispatch={null}
error={null}
listings={listingsMock}
locate={mockLocate}
displayListing={mockDisplayListing}
/>);
it('should render a component matching the snapshot', () => {
const tree = toJson(wrapper);
expect(tree).toMatchSnapshot();
expect(wrapper).toHaveLength(1);
expect(wrapper.find(Header)).toHaveLength(1);
expect(wrapper.find(Hero)).toHaveLength(1);
});
});
});
Required props:
App.propTypes = {
isLocating: PropTypes.bool.isRequired,
location: PropTypes.object,
regions: PropTypes.object,
dispatch: PropTypes.any,
error: PropTypes.object,
};
App.defaultProps = {
isLocating: false,
location: {},
regions: {},
error: {},
};

Testing functions in jest

I need some advice on testing functions in terms of how and what
say I have some state.
state = {
categories: [this is full of objects],
products: [this is also full of objects]
}
then I have this function:
filterProducts = () => {
return this.state.products.filter((product => (
product.categories.some((cat) => (
cat.title == this.state.chosenCategory
))
)))
}
this function filters the products array by working out if the products are part of the selected category.
how would you test this?
I've tried this
let productsStub = [
{id: 1, title: 'wine01', showDescription: false},
{id: 2, title: 'wine02', showDescription: false},
{id: 3, title: 'wine03', showDescription: false}
]
wrapper = shallow(<Menu
categories={categoriesStub}
products={productsStub}
/>);
it('should filter products when searched for', () => {
const input = wrapper.find('input');
input.simulate('change', {
target: { value: '01' }
});
expect(productsStub.length).toEqual(1);
});
what this test (I think) is saying, when I search for 01, I expect the product state (well the stub of the state) to filter and return only 1 result. however the test fails and says expected: 1 received: 3 i.e. the filtering isn't working.
I know I could also do wrapper.instance.filterProducts() but again, I'm not very comfortable on function testing in jest.
any advice? would be great to chat it through with someone
thanks
I replicated your problem statement, but not sure how are you maintaining the state model (props/state). But this might help. :)
Checkout the working example here: https://codesandbox.io/s/6zw0krx15k
import React from "react";
export default class Hello extends React.Component {
state = {
categories: [{ id: 1 }],
products: this.props.products,
selectedCat: 1,
filteredProducts: []
};
filterProducts = value => {
let filteredVal = this.props.products.filter(
product => product.id === parseInt(value)
);
this.setState({
filteredProducts: filteredVal
});
};
setValue = e => {
this.setState({
selectedCat: e.target.value
});
this.filterProducts(e.target.value);
};
render() {
return (
<div>
Filter
<input value={this.state.selectedCat} onChange={this.setValue} />
</div>
);
}
}
import { shallow } from "enzyme";
import Filter from "./Filter";
import React from "react";
let productsStub = [
{ id: 1, title: "wine01", showDescription: false },
{ id: 2, title: "wine02", showDescription: false },
{ id: 3, title: "wine03", showDescription: false }
];
let wrapper = shallow(<Filter products={productsStub} />);
it("should filter products when searched for", () => {
const input = wrapper.find("input");
input.simulate("change", {
target: { value: "1" }
});
expect(wrapper.state().filteredProducts.length).toEqual(1);
});

Categories

Resources