Testing Material UI Radio Checked Value - reactjs

I have a functional component written in React with Redux using React
Hooks.
I am testing using Jest with Enzyme.
The component renders Material UI Radio buttons (code example below):
<RadioGroup>
<FormControlLabel
value="batchName"
label="Batch Name"
name="batchName"
control={
<Radio
disableRipple
name="batchName"
color="primary"
checked={searchBy === 'batchName'}
onClick={() => {dispatch(actions.setBatchSearchBy('batchName'))}}
/>
}
/>
<FormControlLabel
value="firstPaymentDate"
label="First Payment Date"
name="firstPaymentDate"
control={
<Radio:
disableRipple
name="firstPaymentDate"
color="primary"
checked={searchBy === 'firstPaymentDate'}
onClick={() => {dispatch(actions.setBatchSearchBy('firstPaymentDate'))}}
/>
}
/>
</RadioGroup>
Test file:
import React from 'react';
import { BatchHeaderComponent } from '../../../components/batchManagement/BatchHeaderComponent';
import configureStore from '../../../store';
import {Provider} from "react-redux";
import Enzyme, { mount } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import Radio from "#material-ui/core/Radio";
Enzyme.configure({ adapter: new Adapter() });
describe('BatchHeaderComponent', () => {
it('mounts to the DOM with its sub-components', () => {
const wrapper = mount(<Provider store={configureStore()}>
<BatchHeaderComponent/>
</Provider>);
expect(wrapper.find(BatchHeaderComponent)).toBeDefined();
});
it('changes searchBy when a new option has been selected', () => {
const wrapper = mount(<Provider store={configureStore()}>
<BatchHeaderComponent />
</Provider>);
const radio = wrapper.find(Radio).last();
console.log(radio.debug());
// radio.simulate('change', {target: {name: 'firstPaymentDate', checked: true}});
// radio.prop('onChange', {target: { name: 'firstPaymentDate', checked: true}});
radio.simulate('click');
console.log(radio.debug());
expect(radio.props().checked).toEqual(true);
});
});
I can't get the 'checked' value to change when simulating a 'click' or 'change' event.
Regardless of which path I take, the checked value remains false.
Any advice would be greatly appreciated.
Thanks!

I figured it out. I needed to run wrapper.find again to see the updated change.
it('changes searchBy when a new option has been selected', () => {
const wrapper = mount(<Provider store={configureStore()}>
<BatchHeaderComponent />
</Provider>);
const radio = wrapper.find(Radio).last();
radio.simulate('click');
expect(wrapper.find(Radio).last().props().checked).toEqual(true);
});

Related

How to test Material ui's DatePicker with Jest and Enzyme

i've got a problem. I'm trying to test material ui's datePicker (https://mui.com/api/date-picker/) with Jest and Enzyme. I've searched alot but couldnt find anything that would help me... I hope that you will guide me.
Here's what i got:
DatePickerFilter.tsx
import React, {useState} from 'react';
import AdapterDateFns from '#mui/lab/AdapterDateFns';
import LocalizationProvider from '#mui/lab/LocalizationProvider';
import DatePicker from '#mui/lab/DatePicker';
import TextField from '#mui/material/TextField';
import styled from '#emotion/styled';
export const StyledDatePicker = styled(DatePicker)`
width: 320px !important;
`;
const DatePickerFilter = () => {
const [date, setDate] = useState('10/10/2021')
const handleChange = (newValue: any) => {
setDate(newValue)
};
return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<StyledDatePicker
label="Date"
views={['day']}
value={date}
onChange={handleChange}
renderInput={(params) => <TextField {...params} />}
/>
</LocalizationProvider>
);
};
MyBookings.test.tsx
export default DatePickerFilter;
import React from 'react';
import { mount } from 'enzyme';
import { Render } from '../../../utilities/TestsUtils';
import DatePickerFilter from '../../common/DatePickerFilter';
describe('MyBookings > DatePickerFilter', () => {
it('should not change date if its before today', () => {
const wrapper = mount(
<Render>
<DatePickerFilter />
</Render>
);
wrapper.find('input').simulate('change', {target: {value: '11/10/2021'}});
console.log(wrapper.find('input').debug())
wrapper.unmount();
});
});
And here's a function util that gives me access to redux's store
export const store = configureStore({
reducer: {
booking: bookingReducer,
myBookings: myBookingsSlice,
officeSpaces: officeSpacesSlice,
filters: filtersSlice
},
});
export const Render = ({ children }: any) => {
return (
<Provider store={store}>
<ThemeProvider theme={theme}>{children}</ThemeProvider>
</Provider>
);
};
I'm trying to change input's value by simulate 'change' event, but it doesnt want to change.
Here's log of wrapper.find('input').debug()
<input aria-invalid={false} aria-describedby={[undefined]} autoComplete={[undefined]}
autoFocus={false} defaultValue={[undefined]} disabled={[undefined]} id={[undefined]}
onAnimationStart={[Function: handleAutoFill]} name={[undefined]} placeholder={[undefined]}
readOnly={true} required={false} rows={[undefined]} value="10/10/2021"
onKeyDown={[Function (anonymous)]} onKeyUp={[undefined]} type="text" aria-readonly={true}
aria-label="Choose date, selected date is Oct 10, 2021" onClick={[Function: openPicker]}
className="MuiOutlinedInput-input MuiInputBase-input css-1t8l2tu-MuiInputBase-input-MuiOutlinedInput-input"
onBlur={[Function: handleBlur]} onChange={[Function: handleChange]} onFocus={[Function: handleFocus]} />
The answer to this problem is mentioned here https://github.com/mui/material-ui/issues/27038.
If you don't need the Mobile variants, you can do what I did and import the DesktopDatePicker. This way I don't reproduce the problem with triggerin a change on an input.

React-testing: unable to detect DatePicker field to initiate change

I have a component which uses the react-datepicker package.
I am writing to write a unit test which will edits the dates and thereafter run some logic. However, i am unable to detect the field which for me to change using userEvent.type(). I have tried to use getByText, getByRole, getAllByText.
Form.tsx
import React, { useState } from 'react';
import DatePicker from "react-datepicker";
import { Form } from 'react-bootstrap';
import "react-datepicker/dist/react-datepicker.css";
const Form = () => {
const [data, setData] = useState({ date1: new Date(), date2: new Date() })
return (
<div>
<Form>
...some other fields
<Form.Group controlId="date1">
<Form.Label>Date1</Form.Label>
<DatePicker name='date1'selected={data.date1} onChange={(date: Date) => setData({...data, date1: date})}
</Form.Group>
<Form.Group controlId="date2">
<Form.Label>Date2</Form.Label>
<DatePicker name='date2' selected={data.date2} onChange={(date: Date) => setData({...data, date2: date})}
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</div>
)
}
export default Form
Form.test.tsx
import React from 'react';
import Form from './Form';
import {render} from '#testing-library/react';
import userEvent from '#testing-library/user-event';
describe('Form Component', () => {
it('able to change the date', () => {
const { getByRole } = render(<Form/>)
const date1Field = getByRole('textbox', { name: /date1/i })
act(() => userEvent.type(date1Field, '01/01/1990'))
... any other action to submit the form
})
})
However, my terminal showed me, which is the same for both date, which it was unable to detect the input field:
TestingLibraryElementError: Unable to find an accessible element with the role"textbox" and name "/date1/i"
textbox:
Name=""
<input
class=''
name='date1'
type='text'
value='05/23/2021'
/>
Name=""
<input
class=''
name='date2'
type='text'
value='05/23/2021'
/>
There is an issue with the library itself and not being able to pass in any ARIA props into the datepicker in react-datepicker.
With using the other library you mentioned react-day-picker it is possible to pass props into the input and set aria labels.
import DayPickerInput from 'react-day-picker/DayPickerInput';
<DayPickerInput inputProps={{'aria-label':'Date input 2'}} />
Sandbox: https://codesandbox.io/s/react-day-picker-examplesinput-forked-lj8pp
For anyone who is looking for a solution which I have adopted Jonathan S. answer,
Form.tsx
import React, { useState } from 'react';
import DayPickerInput from "react-datepicker/DayPickerInput";
import { Form } from 'react-bootstrap';
import "react-day-picker/lib/style.css";
const Form = () => {
const [data, setData] = useState({ date1: new Date(), date2: new Date() })
return (
<div>
<Form>
...some other fields
<Form.Group controlId="date1">
<Form.Label>Date1</Form.Label>
<DayPickerInput inputProps={{ 'aria-label': 'date1' }} value={data.date1} onChange={(date: Date) => setData({...data, date1: date})}/>
</Form.Group>
<Form.Group controlId="date2">
<Form.Label>Date2</Form.Label>
<DayPickerInput inputProps={{ 'aria-label': 'date2' }} value={data.date2} onChange={(date: Date) => setData({...data, date2: date})}/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</div>
)
}
export default Form
Form.test.tsx
import React from 'react';
import Form from './Form';
import {render} from '#testing-library/react';
import userEvent from '#testing-library/user-event';
describe('Form Component', () => {
it('able to change the date', () => {
const { getByLabelText } = render(<Form/>)
let date1Field = getByLabelText('date1') as HTMLInputElement
// Selects your default value of the date field
date1Field.setSelectRange(0, date1Field.value.length)
// Replaces it
userEvent.type(date1Field, '1990-01-01')
... any other action to submit the form
})
})
It might not be needed but in case, you can pass a prop placeholderText='some text' and then get the input using screen.getByPlaceholderText('some text');
try this solution, this perfectly works for me
const startDate = await container.find('.ant-picker-input input').first();
await startDate.simulate('mousedown');
await waitFor(async () => {
await container.update();
const dateChart = await container.find('.ant-picker-today-btn');
await dateChart.simulate('click');
});
After Trying many solution, and trying and error.
I found a solution that work perfectly fine for me.
describe('Date Picker Test', () => {
let wrapper;
beforeEach(() => {
wrapper = mount(
<Provider store={store}>
<Router>
<DatePikerFunction />
</Router>
</Provider>,
);
});
it('Date Picker Change', async () => {
const datePicker = await wrapper.find('.ant-picker-input input').first();
await datePicker.simulate('mousedown');
await waitFor(async () => {
await wrapper.update();
const today = await wrapper.find('.ant-picker-cell-today'); // put classname that is used to select the perticular date
const next = today.nextElementSibling;
await next.click();
});
});
});
Here I have find the today's date and then selected tomorrow date.
You can find base on class or any thing you want and use it.
Hope It will work for you too.
Thanks

How to use test-id in Material UI Textfield

I'm trying to test a material ui text field using react-testing-library.
The issue im facing is that in order to test the material ui textField i would have to use this property method
screen.getByLabelText()
which works, however i do not want to display the label on the UI, i want the label to remain hidden, as im already using Material UI <FormLabel>.
I tried using inputProps and passing data-testId on the element, using the getByTestId() method. but i get this error
TestingLibraryElementError: Found multiple elements by:
[data-testid="bio"]
(If this is intentional, then use the `*AllBy*` variant of the query (like `queryAllByText`, `getAllByText`, or `findAllByText`)).
editForm.test.tsx
import "#testing-library/jest-dom";
import React from "react";
import { createMount } from "#material-ui/core/test-utils";
import Button from "#material-ui/core/Button";
import Typography from "#material-ui/core/Typography";
import EditProfileForm from "./editForm";
import { render as testRender, fireEvent, screen, getByText } from "#testing-library/react";
const props = {
handleBio: jest.fn(),
};
describe("<EditProfileForm/>", () => {
let wrapper;
let mount;
beforeEach(() => {
mount = createMount();
wrapper = mount(<EditProfileForm {...props} />);
});
it("should render <EditProfileForm/>", () => {
expect(wrapper).toHaveLength(1);
});
it("calls handleBio on bio TextField change", () => {
const input = screen.getByLabelText("bio");
fireEvent.change(input, { target: { value: "new value" } });
expect(props.handleBio).toHaveBeenCalledTimes(1);
});
});
editForm.tsx
import Button from "#material-ui/core/Button";
import FormGroup from "#material-ui/core/FormGroup";
import FormLabel from "#material-ui/core/FormLabel";
import TextField from "#material-ui/core/TextField";
import Typography from "#material-ui/core/Typography";
import React from "react";
const EditProfileForm = (props: any) => (
<form onSubmit={props.onSubmit}>
<Typography variant="h5">Edit Profile</Typography>
<FormGroup style={{ padding: "30px 0px" }}>
<FormLabel style={{ display: "block" }}>Bio</FormLabel>
<TextField
id="outlined-name"
style={{
width: "100%",
}}
name="bio"
label="bio"
multiline={true}
rows="3"
defaultValue={props.bio}
onChange={props.handleBio}
margin="normal"
variant="outlined"
/>
</FormGroup>
<Button className="subBtn" variant="outlined" color="primary" type="submit">
Submit
</Button>
</form>
);
export default EditProfileForm;
I was able to resolve this issue by first moving the test function after beforeEach been called.
so it will now be
import "#testing-library/jest-dom";
import React from "react";
import { createMount } from "#material-ui/core/test-utils";
import Button from "#material-ui/core/Button";
import Typography from "#material-ui/core/Typography";
import EditProfileForm from "./editForm";
import { render as testRender, fireEvent, screen, getByText } from "#testing-library/react";
const props = {
handleChange: jest.fn(),
onSubmit: jest.fn(),
bio: "test",
gravatar: "https://i.pravatar.cc/150?img=3",
handleBio: jest.fn(),
handleGravatar: jest.fn(),
};
describe("<EditProfileForm/>", () => {
let wrapper;
let mount;
beforeEach(() => {
mount = createMount();
wrapper = mount(<EditProfileForm {...props} />);
});
// must be called first
it("calls handleBio on bio TextField change", () => {
const input = screen.getByTestId("bio");
fireEvent.change(input, { target: { value: "new value" } });
expect(props.handleBio).toHaveBeenCalledTimes(1);
});
it("should render <EditProfileForm/>", () => {
expect(wrapper).toHaveLength(1);
});
it("should check header title ", () => {
expect(wrapper.find(Typography).at(0)).toHaveLength(1);
expect(
wrapper
.find(Typography)
.at(0)
.text(),
).toContain("Edit Profile");
});
it("should test bio prop", () => {
expect(wrapper.props().bio).toContain("test");
});
it("should test gravtar prop", () => {
const link = "https://i.pravatar.cc/150?img=3";
expect(wrapper.props().gravatar).toContain(link);
});
it("should test handleChange props", () => {
const title = "Test";
expect(
wrapper.props().handleChange({
target: {
value: title,
},
}),
);
expect(props.handleChange).toHaveBeenCalled();
});
it("should test onSubmit prop", () => {
// console.log(wrapper.find(TextField).debug());
const submit = jest.fn();
wrapper.simulate("submit", { submit });
expect(props.onSubmit).toBeCalled();
});
it("should test button click", () => {
const button = wrapper.find(Button);
button.simulate("click");
expect(props.onSubmit).toBeCalled();
});
});
And then passing data-testid as an input prop on text field like this
<TextField
id="outlined-name"
className="bio-test"
style={{
width: "100%",
}}
name="bio"
inputProps={{
"data-testid": "bio",
}}
multiline={true}
rows="3"
defaultValue={props.bio}
onChange={props.handleBio}
margin="normal"
variant="outlined"
/>

Click a material-ui radio button in a jest test and see it's effect

I am trying to complete a jest test on a bespoke reusable component but having difficulty getting it to work. I have pared it back in a sandbox using plain material-ui components and am still having difficulty in simulating the radio button click. I have tried
wrapper.find(Radio).first().simulate('click');
wrapper.find(Radio).first().prop('onChange', { target: { checked: true } });
wrapper.find(Radio).first().simulate('change', {target: {checked: true}});
wrapper.find(Radio).first().update();
working sandbox with failing test here: https://codesandbox.io/s/magical-raman-7f8v7
complete code file here:
import React from "react";
import { mount } from "enzyme";
import { RadioGroup, Radio } from "#material-ui/core";
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import FormControl from "#material-ui/core/FormControl";
import FormLabel from "#material-ui/core/FormLabel";
configure({ adapter: new Adapter() });
const TestComponentRadioGroup = () => {
const [value, setValue] = React.useState("other");
const handleChange = event => {
setValue(event.target.value);
};
return (
<div>
<FormControl component="fieldset">
<FormLabel component="legend">Gender</FormLabel>
<RadioGroup
aria-label="gender"
name="gender1"
value={value}
onChange={handleChange}
>
<FormControlLabel value="female" control={<Radio />} label="Female" />
<FormControlLabel value="male" control={<Radio />} label="Male" />
<FormControlLabel value="other" control={<Radio />} label="Other" />
</RadioGroup>
</FormControl>
</div>
);
};
describe("---RadioGroup Interaction Test Suite", () => {
test("Test to check selection isupdated from the last option to the first ", () => {
const wrapper = mount(<TestComponentRadioGroup />);
const radioButtonGroup = wrapper.find(RadioGroup);
expect(radioButtonGroup).toHaveLength(1);
//check that the first item isn't checked but the third one is
const radioButtons = wrapper.find(Radio);
console.log("we found " + radioButtons.length + " radiobuttons");
expect(radioButtons).toHaveLength(3);
expect( wrapper.find(Radio).first().props().checked).toBe(false);
expect( wrapper.find(Radio).last().props().checked).toBe(true);
//Desperation - I expected the first one to work!
wrapper.find(Radio).first().simulate("click");
wrapper.find(Radio).first().prop("onChange", { target: { checked: true } });
wrapper.find(Radio).first().simulate("change", { target: { checked: true } });
wrapper.find(Radio).first().update();
//I am not sure that I need this!
wrapper.update();
expect( wrapper.find(Radio).first().props().checked).toBe(true);
expect( wrapper.find(Radio).last().props().checked).toBe(false);
});
});
and react-testing library this will do the work for you.
I am using data-test-id to target the radio input.
it("Radio group change value and new value updated and last value no more checked", () => {
const { container, getByTestId } = render(<FilterRadioButtonGroup {...props} />);
// Before change selection
const allValueRadioButton = getByTestId('radio-button-all');
expect(allValueRadioButton.checked).toEqual(true);
// Change selection
const withValueRadioButton = getByTestId('radio-button-with');
fireEvent.click(withValueRadioButton, { target: { checked: true }});
expect(withValueRadioButton.checked).toEqual(true);
// Old value is no more checked
expect(allValueRadioButton.checked).toEqual(false);
});
if you also use material-ui use the inputProps prop, in order to get data-test-id showed. (As their props accepting).
<Radio
size={size}
disabled={item.disabled}
inputProps={{
"data-testid": `radio-button-${item.text}`,
}}/>
If you not using material-ui no need for the inputProps just directly data-testid
:)
This code is working for me:
import { Radio } from '#material-ui/core';
let mComponent: ReactWrapper;
mComponent = mount(component);
const radioInputs = mComponent.find(Radio);
radioInputs.at(0).simulate('click', { target: { checked: true } });

Enzyme unit testing onChange method using Material UI Components

How would i be able to unit-test onChange method on this component.
Comment.js
import React from "react";
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
const Comment = (props) => (
<div>
<form onSubmit={props.onSubmit}>
<TextField
type="text"
id="outlined-multiline-static"
label="Write A Comment"
multiline
name="comment_body"
value={props.commentBody}
rows="10"
fullWidth
margin="normal"
variant="outlined"
onChange={props.commentChange}
/>
{/* <Button type="submit" variant="outlined" component="span" color="primary">
Post A Comment
</Button> */}
<button type="submit" variant="outlined" component="span" color="primary">
Write a Comment
</button>
</form>
</div>
)
export default Comment;
This is my attempt to unit test the onChange component, getting a
Method “simulate” is meant to be run on 1 node. 0 found instead
around this line
const component = shallow(<Comment commentChange={onChangeMock} commentBody={'test'} />)
component.find('input').simulate('change');
Comment.test.js
import React from 'react';
import ReactDOM from 'react-dom';
import { shallow } from 'enzyme';
import Comment from './Comment';
describe('Should render <Comment/> component', () => {
it('Should render form', () => {
const wrapper = shallow(<Comment/>)
// wrapper.find('Form').at(0)
expect(wrapper.find("form")).toHaveLength(1); // checks if there is a form.
})
it('Should render button', () => {
const wrapper = shallow(<Comment/>)
expect(wrapper.find('button')).toHaveLength(1);
})
it('should check for onChange method', () => {
// const wrapper = shallow(<Comment onChange={}/>)
const onChangeMock = jest.fn();
// const event = {
// preventDefualt(){},
// target: {
// value: 'testing'
// }
// }
const component = shallow(<Comment commentChange={onChangeMock} commentBody={'test'} />)
component.find('input').simulate('change');
expect(onChangeMock).toBeCalledWith('test')
})
})
The Comment component is being passed in another component like this:
ImageContainer.js
state = {
isComment: false,
comment_body: ""
}
handleCommentChange = (e) => {
this.setState({
comment_body: e.target.value
})
}
commentSubmit = (event, id) => {
event.preventDefault();
console.log(this.state.comment_body); // doesn't get console.log
// note that commentBody is being used for the req.body as well so its called by req.body.commentBody
const commentBody = this.state.comment_body
const data = {
commentBody,
id
}
this.props.postComment(data);
this.setState({
comment_body: ''
})
}
<Comment onSubmit={(e) => this.commentSubmit(e, img.id)}
commentBody={this.state.comment_body }
commentChange={this.handleCommentChange}/>
The reason you are having the error is because when you call component.find('input') it returns an array of matched components, so what you want to do is
component.find('input').at(0).simulate('change')
However, there is another way you can test this, which is my preferred method.
component.find('input').at(0).props().onChange()
Below is the correct way to do the test with both methods
import React from "react";
import Enzyme, { shallow } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import Comment from "./Comment";
import TextField from "#material-ui/core/TextField";
Enzyme.configure({ adapter: new Adapter() });
describe("Should render <Comment/> component", () => {
it("should check for onChange method (1)", () => {
// const wrapper = shallow(<Comment onChange={}/>)
const onChangeMock = jest.fn();
const component = shallow(
<Comment commentChange={onChangeMock} commentBody={"test"} />
);
component
.find(TextField)
.at(0)
.simulate("change", "test");
expect(onChangeMock).toBeCalledWith("test");
});
it("should check for onChange method (2)", () => {
// const wrapper = shallow(<Comment onChange={}/>)
const onChangeMock = jest.fn();
const component = shallow(
<Comment commentChange={onChangeMock} commentBody={"test"} />
);
component
.find(TextField)
.at(0)
.props()
.onChange();
expect(onChangeMock).toBeCalled();
});
});
For this particular test it will be better if you just use toBeCalled rather than toBeCalledWith. There is no need to test the value it is called with.

Resources