Languages

React Testing Library - fireEvent doesn't change input value

3 points
Asked by:
Michał Sukowski
1060

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.

2 answers
5 points
Answered by:
Michał Sukowski
1060

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:

  1. React - form with useState example (controlled components)

  2. React - form example (uncontrolled components)

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ł");
});
2 comments
Michał Sukowski
Thank you very much for your answer and your help! Unfortunanently, test is still doesn&#39;t work... I don&#39;t know why. My app is working corretly ofcourse. My inputs are controlled. But every input is a component (label and input) which I&#39;m importing to BuyPlace component. Maybe this is a problem? I don&#39;t know. I&#39;m passing props like name, id, placeholder and function which changes state (value). On &#34;live&#34; everything is working like a gold - but on test I&#39;m always getting empty string. I&#39;m very curious why.
Sameer Mishra
Actually it&#39;s your working, but only if we don&#39;t mock the useState, however it&#39;s not working when we use useState mocking (which is required by others test cases). any insight?
Add comment
3 points
Answered by:
Michał Sukowski
1060

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!

1 comments
Root-ssh
State management and rendering cycles for each component are more complicated than than it could seem. I think, you should check if states in the components are managed in the correct way. Consider to check our https://dirask.com/posts?hashtags&#61;react or https://dirask.com/posts?hashtags&#61;react,state posts.
Add comment
Donate to Dirask
Our content is created by volunteers - like Wikipedia. If you think, the things we do are good, donate us. Thanks!
Join to our subscribers to be up to date with content, news and offers.
Native Advertising
🚀
Get your tech brand or product in front of software developers.
For more information Contact us
Dirask - we help you to
solve coding problems.
Ask question.

❤️💻 🙂

Join