'React-testing-library: changes due to input

I'm trying to test that a component updates as it should due to changes in an input element. I use the fireEvent.change()-function, and if I then check the value of the node I found using getByPlaceholderText it has updated as it should. However I cannot see the changes in the react component itself.

This might be because the changes don't happen until a rerender; how would I test this? react-testing-library's rerender appears to start the component "from scratch" (i.e. without the new input value), and waitForElement never finds what it's waiting for.

Here's the component TestForm.js:

import React from 'react';
import { withState } from 'recompose';

const initialInputValue = 'initialInputValue';

const TestForm = ({ inputValue, setInputValue }) => (
  <>
    {console.log('inputValue', inputValue)}
    <input value={inputValue} onChange={(e) => setInputValue(e.target.value)} placeholder="placeholder" />
    {inputValue !== initialInputValue && <div>Input has changed</div>}
  </>
);

export default withState('inputValue', 'setInputValue', initialInputValue)(TestForm);

And here's the test, run using npx jest test.js:

import React from 'react';
import { cleanup, fireEvent, render, waitForElement } from 'react-testing-library';

import TestForm from './TestForm';

afterEach(cleanup);

describe('TestForm', () => {
  it('Change input', async () => {
    const { getByPlaceholderText, getByText } = render(<TestForm />);
    const inputNode = getByPlaceholderText('placeholder');
    fireEvent.change(inputNode, { target: { value: 'new value' } });
    console.log('inputNode.value', inputNode.value);
    await waitForElement(() => getByText('Input has changed'));
  });
});


Solution 1:[1]

This code works for me:

import React from "react";

const initialInputValue = "initialInputValue";

class TestForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = { inputValue: initialInputValue };
  }
  render() {
    const { inputValue } = this.state;

    return (
      <div>
        {console.log("inputValue", inputValue)}
        <input
          value={inputValue}
          onChange={e => this.setState({ inputValue: e.target.value })}
          placeholder="placeholder"
        />
        {inputValue !== initialInputValue && <div>Input has changed</div>}
      </div>
    );
  }
}

import { render, cleanup, fireEvent } from "react-testing-library";
import "jest-dom/extend-expect";

afterEach(cleanup);

test("form", () => {
  const { getByPlaceholderText, getByText } = render(<TestForm />);
  fireEvent.change(getByPlaceholderText("placeholder"), {
    target: { value: "new value" }
  });
  expect(getByText("Input has changed")).toBeInTheDocument();
});

It does not work in codesandbox though, I guess they have some issues with keeping the browser and the test environment separated.

Solution 2:[2]

using user-event library

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

afterEach(cleanup);

test("form", async () => {
  const user = userEvent.setup();
  const { getByPlaceholderText, getByText } = render(<TestForm />);
  
  await user.type(getByPlaceholderText("placeholder"), "new value");
  
  await waitFor(() => {
    expect(getByText("Input has changed")).toBeInTheDocument();
  });
});

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 samehanwar