Home > Blockchain >  React/Jest Select element onChange values not updating
React/Jest Select element onChange values not updating

Time:02-14

I have the following code to test the value of the Select element after changing:

it("changes value after selecting another field", () => {
  doSetupWork();
  let field = screen.getByLabelText("MySelectField");
  expect(field).toHaveValue("");

  fireEvent.change(field, { target: { value: "1" } });

  // Insert one of two options from below
});

However, when I insert the following at the bottom, it does not work:

field = screen.getByLabelText("MySelectField");
expect(field).toHaveValue("1");

and gives the following error message:

Expected the element to have value: 1
Received:

But, when I wrap it in a setTimeout with just 1ms delay, it does work:

setTimeout(() => {
  field = screen.getByLabelText("MySelectField");
  expect(field).toHaveValue("1");
}, 1);

It feels like there should be a more elegant way of writing this without setTimeout. Any advice?

CodePudding user response:

You could try using waitFor instead of setTimeout:

import {waitFor} from '@testing-library/react'

...

await waitFor(() => screen.getByLabelText("MySelectField").toHaveValue("1"))

CodePudding user response:

What you're seeing is that it takes a finite amount of time for your app to react (excuse the pun) to the change that has occurred - specifically, it needs to re-render.

And yes, there is a nicer way - the waitFor function from testing-library/react:

import { screen, waitFor } from '@testing-library/react';

...

  it("changes value after selecting another field", async () => {

    ...

    fireEvent.change(field, { target: { value: "1" } });

    await waitFor(async () => {
      field = screen.getByLabelText("MySelectField");
      expect(field).toHaveValue("1");
    });
  }

Note that the entire test body (i.e. after the test's name) has to be declared async in order to be able to await the new waitFor block.

CodePudding user response:

When I am using react-testing-library I tend to use render when I have events to interact with. For instance:

In my App.js I have this code on the return method

  const handleChoice = () => {};

  const attributes = [
    { label: "One", value: "1" },
    { label: "Two", value: "2" },
    { label: "Three", value: "3" }
  ];

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>

      <select onChange={handleChoice} data-testid="MySelectField">
        <option value="0">Zero</option>
        {attributes.map((item) => {
          return (
            <option key={item.value} value={item.value}>
              {item.label}
            </option>
          );
        })}
      </select>
    </div>
  );

then my test would be something like this:

import { fireEvent, render } from "@testing-library/react";
import "@testing-library/jest-dom";
    
import App from "./App";
    
it("changes value after selecting another field", () => {
  const { getByTestId } = render(<App />);
  let field = getByTestId("MySelectField");
  expect(field).toHaveValue("0");

  fireEvent.change(field, { target: { value: "1" } });
  expect(field.value).toBe("1");

  fireEvent.change(field, { target: { value: "3" } });
  expect(field.value).toBe("3");
  // Insert one of two options from below
});

Take a look in this sandbox to see it working. https://codesandbox.io/s/ecstatic-https-hzi5n?file=/src/App.spec.js

  • Related