'How to test a required input field with React Testing Library and Jest?

I tried to test a required input field with React Testing Library and Jest by testing the existence of the popover, but I failed. I tried several variants and none is working. The steps from UI are the following:

  1. Click the empty field Empty select field
  2. Click next to the field (blur)
  3. Mouse over the field
  4. Get the required message Required message

The testing code for making that happen is:

  const input = screen.getByRole('textbox', {name: /name \*/i})
  expect(input).toBeTruthy();
  fireEvent.click(input);
  fireEvent.blur(input);
  await waitFor(() => expect(input).toHaveValue(config.EMPTY_STRING));

  act(() => {
    fireEvent.mouseOver(input);
  });

  await waitFor(() => {
    expect(screen.getByText('Name is required')).toBeTruthy();
  });

Unfortunately, it doesn't work and I get this error: TestingLibraryElementError: Unable to find an element with the text: Name is required. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

I changed the last line this way: expect(screen.getByText('Name is required')).toBeInTheDocument(); but got the same error.

I tried with expect(screen.findByText('Name is required')).toBeInTheDocument(); but got the same error.

My last attempt was with: expect(screen.findByText('Name is required')).toBeTruthy();. Here the test for the field passed but the overall test failed. The error I got is: console.error Warning: You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. Error: Uncaught [TypeError: Cannot read property 'useRealTimers' of null]

So I got stucked. Any help would be much appreciated. Thanks a lot!



Solution 1:[1]

I don't think this is possible as of writing because the popup for HTML5 Validation is handled by the browser.

Jest is a Node-based runner. The create-react-app docs describes it best:

Jest is a Node-based runner. This means that the tests always run in a Node environment and not in a real browser. This lets us enable fast iteration speed and prevent flakiness.

While Jest provides browser globals such as window thanks to jsdom, they are only approximations of the real browser behavior. Jest is intended to be used for unit tests of your logic and your components rather than the DOM quirks.

We recommend that you use a separate tool for browser end-to-end tests if you need them.

If you'd like to test the popup, you can use the right tool for the job, like Cypress. You can refer to this.

^ This is the method I use. Then in testing-library I test "my work" i.e.

Actually triggering the browser-implementation of the validation is not really testing your work (but instead testing the browser itself), which might be nice but in my opinion is unnecessary.

So in your example, since input is required and you're expecting the popup to appear, it can be assumed the input's value is still empty. So we write an assertion for that:

expect(input).toHaveValue("")
// I also add some css to highlight the required input,
// so I assert on that
expect(input).toHaveClass("is-required")

Solution 2:[2]

jest-dom now has a toBeValid and toBeInvalid custom matchers.

it("should show the validity", async () => {
  let { user } = prep(<ComponentToTest />)
  let requiredRadioOption = screen.getByLabelText("radio-option-0")

  expect(requiredRadioOption).toBeInvalid()

  await user.click(requiredRadioOption)

  expect(requiredRadioOption).toBeValid()
})

Solution 3:[3]

You can test the HTML5 built-in validation with React Testing Library using querySelector and the pseudoclass selector:

:valid

Example:

import { render, screen } from "@testing-library/react";
import user from "@testing-library/user-event";
import Client from "./index";

const passedBrowserValidation = (view, name) => {
  const selector = `[name=${name}]:valid`;
  // eslint-disable-next-line testing-library/no-node-access
  const isValidated = view.container.querySelector(selector);
  return Boolean(isValidated);
};

const renderViewNew = () => {
  return render(
    <Client />
  );
};

const getClientName = () => {
  return screen.findByRole('textbox', { name: /Name/i });
};

describe("Client form", () => {
  it("Should validate ok a right value in name", () => {
    const view = renderViewNew();
    const nameField = getClientName();
    user.type(nameField, "Acme");
    expect(passedBrowserValidation(view, "name")).toBeTruthy();
  });
});

Please note that the Testing Library authors try to discourage the use of querySelector but in this case, it may be justified. That's the reason of the comment for the linter: // eslint-disable-next-line testing-library/no-node-access

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 neldeles
Solution 2 Christian Todd
Solution 3 Johann Echavarria