Passing props in a child component using jest mount - reactjs

I am trying to build a small app to test my jest and enzyme knowledge. However, I have run into an issue.
I have the following Homepage.js:
const Homepage = props => {
const [input, setInput] = React.useState({
type: 'input',
config: {
required: true,
placeholder: 'Type a GitHub username'
},
value: ''
});
const onChangeInput = event => {
setInput(prevState => {
return {
...prevState,
value: event.target.value
};
});
};
return (
<section className={styles.Homepage}>
<form className={styles.SearchBox} onSubmit={(event) => props.getUser(event, input.value)} data-test="component-searchBox">
<h4>Find a GitHub user</h4>
<Input {...input} action={onChangeInput}/>
{props.error ? <p>{`Error: ${props.error}`}</p> : null}
<Button>Submit</Button>
</form>
</section>
);
};
I want to test that the input field fires a function when I type in it. The Input.js component is the following:
const input = props => {
switch (props.type) {
case 'input':
return <input className={styles.Input} {...props.config} value={props.value} onChange={props.action} data-test="component-input"/>
default: return null;
};
};
You can find my test below:
const mountSetup = () => {
const mockGetUser = jest.fn()
return mount(
<Homepage type='input' getUser={mockGetUser}>
<Input type='input'/>
</Homepage>
);
};
test('State updates with input box upon change', () => {
const mockSetInput = jest.fn();
React.useState = jest.fn(() => ["", mockSetInput]);
const wrapper = mountSetup();
const inputBox = findByTestAttr(wrapper, 'component-input');
inputBox.simulate("change");
expect(mockSetInput).toHaveBeenCalled();
});
The problem here is that in the Input.js the switch is always returning null even though I am passing the props type='input'. As such I get the error Method “simulate” is meant to be run on 1 node. 0 found instead.
Can someone help me with this?
Thanks

You need not pass the Input inside the HomePage. Homepage has input child and it renders
<Homepage type='input' getUser={mockGetUser}>
</Homepage>
You can try passing the data in the react Usestate mock.
React.useState = jest.fn(() => [{
type: 'input'
}, mockSetInput]);

Related

Using a High Order Component does not send data to the form in React

I want to adjust a demo provided by some tutorial about React Design Patterns, subject: Higher Order Component, and want to use an external data source from the url:
https://jsonplaceholder.typicode.com/users/1
to display the data within my form.
I guess since it's an async call, my Form always displays the "Loading part". What's the best way to solve this issue to ultimately receive the data? I can clearly see response.data not being empty when I log it, but the State variables are when I log them inside of the useEffect Hook
This is what I got so far.
Any help, tips, additional sources to learn this would be highly appreciated.
This is my HOC which I just copied:
import React, { useState, useEffect } from "react";
import axios from "axios";
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
export const withEditableResource = (Component, resourcePath, resourceName) => {
return (props) => {
const [originalData, setOriginalData] = useState(null);
const [editedData, setEditedData] = useState(null);
useEffect(() => {
(async () => {
const response = await axios.get(resourcePath);
setOriginalData(response.data);
setEditedData(response.data);
})();
}, []);
const onChange = (changes) => {
setEditedData({ ...editedData, ...changes });
};
const onSave = async () => {
const response = await axios.post(resourcePath, {
[resourceName]: editedData,
});
setOriginalData(response.data);
setEditedData(response.data);
};
const onReset = () => {
setEditedData(originalData);
};
const resourceProps = {
[resourceName]: editedData,
[`onChange${capitalize(resourceName)}`]: onChange,
[`onSave${capitalize(resourceName)}`]: onSave,
[`onReset${capitalize(resourceName)}`]: onReset,
};
return <Component {...props} {...resourceProps} />;
};
};
That's my form, I want to use - in the last lines you can find the hard-coded URL path, I want to swap for a parameter once this problem is done:
import { withEditableResource } from "./withEditableResource";
export const UserInfoFormImproved = withEditableResource(
({ user, onChangeUser, onSaveUser, onResetUser }) => {
const { name, email, username } = user || {};
return user ? (
<>
<label>
Name:
<input
value={name}
onChange={(e) => onChangeUser({ name: e.target.value })}
/>
</label>
<label>
Email:
<input
value={email}
onChange={(e) => onChangeUser({ email: e.target.value })}
/>
</label>
<label>
Username:
<input
value={username}
onChange={(e) => onChangeUser({ username: e.target.value })}
/>
</label>
<button onClick={onResetUser}>Reset</button>
<button onClick={onSaveUser}>Save Changes</button>
</>
) : (
<p>Loading...</p>
);
},
`https://jsonplaceholder.typicode.com/users/3`,
"User"
);
And that's the actual use of this two components within my App - I've added my idea on how to solve the parameter argument here:
import { UserInfoFormImproved } from "./HigherOrderComponents/UserInfoFormImproved";
function App() {
return (
<UserInfoFormImproved userId={1} />
);
}
export default App;

React Testing Library: fireEvent change is not working

When I tried to change DatePicker input I am getting error.
Here is my files for components and testcase:
datePicker.js
const DatePicker = ({ value, onChange, name, locale }) => {
const myVal = value ? value.locale(locale) : '';
return (
<TextInput
value={myVal}
onChange={onChange}
name={name}
/>)
};
export default DatePicker;
date.js
import React from 'react';
import DatePicker from './datepicker';
const Date = () => {
const [dates, setDate] = useState('');
return (
<DatePicker
name="date"
value={dates}
onChange={(_, value) => setDate(value)}
locale="ja"
/>
); }
date.test.js
test('should render Date properly', () => {
const component = render(<Date />);
const date = component.container.querySelector(
'input[name="date"]'
);
fireEvent.change(date, {
target: {
value: '10/1/2021',
},
});
});
Here is the error which I am getting :
TypeError: value.locale is not a function
fireEvent.change(date, {
^
target: {
value: '10/1/2021',
},
I am personally have the issues with the change fire event however one thing I see that you have wrong is invalid formatting. React testing library specifically states that the target value for that event on a date input type has to be 00-00-000.
https://testing-library.com/docs/dom-testing-library/api-events/
// Invalid:
fireEvent.change(input, {target: {value: '24/05/2020'}})
// Valid:
fireEvent.change(input, {target: {value: '2020-05-24'}})

Testing a function inside a component with jest and testing-library

I'm new with testing-library and jest and I'm trying to test a function inside of the component that changes the value of an input. The component is a form with another component that it's an input.
export const Form = () => {
const [name, setName] = useState("");
const handleOnSubmit = e => {
e.preventDefault();
const form = e.target;
};
const inputChange = (param) => (e) => {
const inputValue = e.target.value;
setName(inputValue);
};
return (
<form className="form" onSubmit={handleOnSubmit}>
<InputGroup text="name" type="text" value={name} functionality={inputChange("name")}/>
<Button type="submit" disabled={name === undefined}/>
</form>
);
};
export default Form;
The InputGroup Component looks like this
export const InputGroup = ({type, id, value, required, functionality, text}) => {
return (
<label>{text}</label>
<input className="input" type={type} id={id} name={id} value={value}
required={required} onChange={functionality}
/>
);
};
I have tried something like this, but I'm not pretty sure on how to test a function that is directly on the component Form and that it's being passed to the component InputGroup.
describe("Form", () => {
let value;
let component;
const handleSubmit = jest.fn();
const handleChange = ev => {
ev.preventDefault();
value = ev.currentTarget.value;
}
beforeEach(() => {
component = render(
<Form onSubmit={handleSubmit} functionality={handleChange} />
);
});
it("check error name is triggered", () => {
const input = component.getByText("name");
fireEvent.change(input, {target: {value: "aaa"}});
});
});
I get an error thats says "The given element does not have a value setter", so how can I pass the inputChange function to the InputGroup Component?
Ok, thanks for sharing the InputGroup. So you can test the InputGroup easily.
describe("InputGroup", () => {
it("check error name is triggered", () => {
const fn = jest.fn()
render(<InputGroup functionality={functionality} />)
fireEvent.change(input, {target: {value: "aaa"}});
expect(fn).toHaveBeenLastCalledWith(...)
});
});
Just an idea. The above code might not run as posted. Ok, now comes to the Form. The step might be similar but it might take you some time to get around, but you don't need to test the functionality again, simply because this prop has nothing to do with Form. From the form perspective, it only cares onSubmit, so you can test against that in the similar way that posted earlier.

asynchronous function test onChange event fails in enzyme and jest

I built a form with formik + material-ui in React app.
and want to test input onChange event with Jest, enzyme and sinon.
I used setTimeout() since Formik's handlers are asynchronous and enzyme's change event is synchronous.
problem
testing 'if the value is displayed on input change' fails.
const input = wrapper.find('#username');
input.simulate('change', { target: { name: 'username', value: 'y' }
setTimeout(() => {
expect(mockChange.calledOnce).toBe(true); // test is passed.
expect(input.props().value).toEqual('y'); // Expected value to equal: "y" , Received: ""
done();
}, 1000);
loginContainer
...
render() {
const values = { username: '', password: '' };
return (
<React.Fragment>
<Formik
initialValues={values}
render={props => <Form {...props} />}
onSubmit={this.handleUserLogin}
validationSchema={loginValidation}
/>
</React.Fragment>
);
}
...
loginForm
import React from 'react';
import TextField from '#material-ui/core/TextField';
...
const styles = theme => ({ });
const Form = props => {
const {
values: { username, password },
errors,
touched,
handleChange,
handleSubmit,
isSubmitting,
handleBlur,
classes,
} = props;
return (
<div className="login-container" data-test="loginformComponent">
<form onSubmit={handleSubmit} className="flex flex-column-m items-center">
<TextField
id="username"
value={username || ''}
onChange={handleChange}
...
/>
<TextField
id="password"
value={password || ''}
onChange={handleChange}
...
/>
...
</form>
</div>
);
};
export const Unwrapped = Form;
export default withStyles(styles)(Form);
loginForm.test
import React, { shallow, sinon } from '../../__tests__/setupTests';
import { Unwrapped as UnwrappedLoginForm } from './loginForm';
const mockBlur = jest.fn();
const mockChange = sinon.spy();
const mockSubmit = sinon.spy();
const setUp = (props = {}) => {
const component = shallow(<UnwrappedLoginForm {...props} />);
return component;
};
describe('Login Form Component', () => {
let wrapper;
beforeEach(() => {
const props = {
values: { username: '', password: '' },
errors: { username: false, password: false },
touched: { username: false, password: false },
handleChange: mockChange,
handleSubmit: mockSubmit,
isSubmitting: false,
handleBlur: mockBlur,
classes: {
textField: '',
},
};
wrapper = setUp(props);
});
describe('When the input value is inserted', () => {
it('renders new username value', done => {
const input = wrapper.find('#username');
input.simulate('change', { target: { name: 'username', value: 'y' } });
setTimeout(() => {
wrapper.update();
expect(mockChange.calledOnce).toEqual(true);
done();
}, 1000);
});
});
});
Try the following:
const waitForNextTick = process.nextTick
waitForNextTick(() => {
expect(mockChange.calledOnce).toBe(true); // test is passed.
expect(input.props().value).toEqual('y'); // Expected value to equal: "y" , Received: ""
done();
});
From the code you have provided, I don't think the value of the input is going to be updated.
You are triggering the change event on TextField component. What this triggers is the onChange callback for the input, which in turn executes the handleChange prop of your UnwrappedLoginForm component. But this does not change the input value per se.

Testing onChange function in Jest

I'm relatively new to Jest and testing in general. I have a component with an input element:
import * as React from "react";
export interface inputProps{
placeholder: string;
className: string;
value: string;
onSearch: (depID: string) => void;
}
onSearch(event: any){
event.preventDefault();
//the actual onclick event is in another Component
this.props.onSearch(event.target.value.trim());
}
export class InputBox extends React.Component<inputProps, searchState> {
render() {
return (
<input
onChange={this.onSearch} //need to test this
className={this.props.className}
type="text"
value={this.props.value}
placeholder={this.props.placeholder} />
);
}
}
I want a test that checks that input element's onChange is a function that takes in the input element's value attribute as the parameter. This is how far I have gotten so far:
//test to see the input element's onchange
//returns a function that takes its value as a param
it("onChange param is the same value as the input value", () => {
const mockFn = jest.fn();
const input = enzyme.shallow(<InputBox
value="TestVal"
placeholder=""
className=""
onSearch={mockFn}/>);
input.find('input').simulate('change', { preventDefault() {} });
expect(mockFn.mock.calls).toBe("TestVal");
});
I am going off of the first solution here Simulate a button click in Jest
And: https://facebook.github.io/jest/docs/en/mock-functions.html
Edit: Running the above throws the following error:
TypeError: Cannot read property 'value' of undefined
Syntax on your code snippet I think should be:
import React from 'react';
export default class InputBox extends React.Component {
onSearch(event) {
event.preventDefault();
this.props.onSearch(event.target.value.trim());
}
render () { return (<input onChange={this.onSearch.bind(this)} />); }
}
The test is failing because, as same you define the preventDefault function on the event object, you also must define other properties used on the onSearch function.
it('should call onChange prop', () => {
const onSearchMock = jest.fn();
const event = {
preventDefault() {},
target: { value: 'the-value' }
};
const component = enzyme.shallow(<InputBox onSearch={onSearchMock} />);
component.find('input').simulate('change', event);
expect(onSearchMock).toBeCalledWith('the-value');
});
Previous test code needs to define the event shape because you are using shallow rendering. If you want instead to test that the actual input value is being used on your onSearch function you need to try a full render with enzyme.mount:
it('should call onChange prop with input value', () => {
const onSearchMock = jest.fn();
const component = enzyme.mount(<InputBox onSearch={onSearchMock} value="custom value" />);
component.find('input').simulate('change');
expect(onSearchMock).toBeCalledWith('custom value');
});
For those testing using TypeScript (and borrowing from the answers above), you'll need to perform a type coercion (as React.ChangeEvent<HTMLInputElement>) to ensure that the linter can view the signature as being compatible:
React file
export class InputBox extends React.Component<inputProps, searchState> {
onSearch(event: React.ChangeEvent<HTMLInputElement>){
event.preventDefault();
//the actual onclick event is in another Component
this.props.onSearch(event.target.value.trim());
}
render() {
return (
<input
onChange={this.onSearch} //need to test this
className={this.props.className}
type="text"
value={this.props.value}
placeholder={this.props.placeholder} />
);
}
}
Test file
it('should call onChange prop', () => {
const onSearchMock = jest.fn();
const event = {
target: { value: 'the-value' }
} as React.ChangeEvent<HTMLInputElement>;
const component = enzyme.shallow(<InputBox onSearch={onSearchMock} />);
component.find('input').simulate('change', event);
expect(onSearchMock).toBeCalledWith('the-value');
});
or alternatively
it('should call onChange prop', () => {
const onSearchMock = jest.fn();
const event = {
target: { value: 'the-value' }
} as React.ChangeEvent<HTMLInputElement>;
const component = enzyme.mount<InputBox>(<InputBox onSearch={onSearchMock} />);
const instance = component.instance();
instance.onSearch(event);
expect(onSearchMock).toBeCalledWith('the-value');
});
I figured out the solution.
So, instead of passing in the value inside InputBox, we have to pass it inside the second param of simulate as shown below. Then we simply check for equality against the first arg of the first call to the mockFn. Also, we can get rid of the event.preventDefault();
it("onChange param is the same value as the input element's value property", () => {
const mockFn = jest.fn();
const input = enzyme.shallow(<InputBox
value=""
placeholder=""
className=""
onSearch={mockFn}/>);
input.find('input').simulate('change', {target: {value: 'matched'} });
expect(mockFn.mock.calls[0][0]).toBe('matched');
});
How about this one? I simulate the change event using enzyme and perform a snapshot test.
Component
import React, { FunctionComponent, useState } from 'react';
const Index: FunctionComponent = () => {
const [val, setVal] = useState('');
const onInputChange = e => {
e.preventDefault();
setVal(e.target.value);
};
return (
<input type='text' onChange={onInputChange} value={val} />
);
};
export default Index;
Unit Test
describe('Index with enzyme', () => {
it('Should set value to state when input is changed', () => {
const container = shallow(<Index />);
const input = container.find('input');
input.simulate('change', { preventDefault: jest.fn, target: { value: "foo" } });
expect(container).toMatchSnapshot();
});
});
Snapshot
exports[`Index with enzyme Should set value to state when input is changed 1`] = `
<input
onChange={[Function]}
type="text"
value="foo"
/>
`;
I struggled with this for hours. Plus since I had multiple select fields on one page. What I found is that Textfield solution works differently from Select.test given on docs.
On the code I defined SelectProps with id. (You can also go with data-testid)
I could only trigger dropdown by clicking this field.
<TextField
select
variant = "outlined"
value = { input.value || Number(0) }
onChange = { value => input.onChange(value) }
error = { Boolean(meta.touched && meta.error) }
open = { open }
SelectProps = {
{
id: `${input.name}-select`,
MenuProps: {
anchorOrigin: {
vertical: "bottom",
horizontal: "left"
},
transformOrigin: {
vertical: "top",
horizontal: "left"
},
getContentAnchorEl: null
}
}
}
{ ...props} >
//yourOptions Goes here
</TextField>
And in my test.
const pickUpAddress = document.getElementById("address-select");
UserEvent.click(pickUpAddress);
UserEvent.click(screen.getByTestId("address-select-option-0"));
Worked like a charm afterwards. Hope this helps.
If you're writing lwc (salesforce) jest tests you can simulate this by selecting the input and dispatching an event.
const changeEvent = new CustomEvent('change', {
detail: {
'value': 'bad name'
}
});
element.shadowRoot.querySelector('lightning-input').dispatchEvent(changeEvent);

Resources