React Testing Library - fireEvent doesn't change input value
I'm learning testing in React, but fireEvent
doesn't change value on my input. Unfotunanently, I don't know why?
I'm rendering BuyPlace
component. In this component I have form which has Input components (these components are simply label with input and I'm passing props to this compoments on form).
On screen.debug()
I can see that my components is rendered corretly with this form and all of these inputs.
Also, my input is catched corretly in my test, I checked this.
This is my test:
test("should change input value", async () => {
render(
<HashRouter>
<UserContext.Provider value={{ user: true }}>
<BuyPlace />
</UserContext.Provider>
</HashRouter>
);
const nameInputEl = screen.getByPlaceholderText("Podaj własną nazwę");
fireEvent.change(nameInputEl, {
target: { value: "Michał" },
});
await waitFor(() => {
expect(nameInputEl.value).toBe("Michał");
});
});
Test is not passing. Ofcourse my app is working corretly, but on test it doesn't work.
This is handleInputChange on my component:
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement> | React.ChangeEvent<HTMLTextAreaElement>) => {
setInputValues((prevValues) => {
return {
...prevValues,
[e.target.name]: e.target.value,
}
});
};
Every solution which I found is not working... My value on input is always empty string. Can someone help me with this? I want to learn to write good tests, but I don't know where the problem is.
It is difficult to find reasone why value
property is empty - more source code is needed. At the first, check the way, how you store input
element state inside BuyPlace
component. It may be caused by incorreclty handled onChange
input
event.
Proposed solution
In the below example I use controlled input component (consider if your input elements work in cotroller or uncontrolled mode).
Check this articles:
Example working sorce code:
App.test.js
file:
import { useState } from "react";
const BuyPlace = () => {
const [name, setName] = useState('');
const handleChange = (e) => {
setName(e.target.value);
};
return (
<div className="BuyPlace">
<input
className="BuyPlace-name"
value={name}
placeholder="Podaj własną nazwę"
onChange={handleChange}
/>
</div>
);
};
export default BuyPlace;
BuyPlace.js
file:
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import BuyPlace from './BuyPlace';
test('should change input value', async () => {
render(
<div>
{/* <HashRouter> */}
{/* <UserContext.Provider value={{ user: true }}> */}
<BuyPlace />
{/* </UserContext.Provider> */}
{/* </HashRouter> */}
</div>
);
const nameInputElement = screen.getByPlaceholderText("Podaj własną nazwę");
fireEvent.change(nameInputElement, {
target: { value: "Michał" },
});
await waitFor(() => {
expect(nameInputElement.value).toBe("Michał");
});
});
Preview:
Useful advise
It is good to test smaller source code pieces to find possible bugs in easier way, e.g.:
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import BuyPlace from './BuyPlace';
test('should change input value', () => {
render(<BuyPlace />);
const nameInputElement = screen.getByPlaceholderText("Podaj własną nazwę");
fireEvent.change(nameInputElement, {
target: { value: "Michał" },
});
expect(nameInputElement.value).toBe("Michał");
});
Wow, after 3 days I found where the problem was!
The problem was in setState function which changes values. I had:
const myValues = (object with values);
setState((previousValues) => {
...previousValues,
[e.target.name]: e.target.value
});
But after changing on this:
setState({
...myValues,
[e.target.name]: e.target.value
});
My test is passing. So I can't use previousState callback on setState - this is why my test was incorrect. But... why? This is a question for me!