Search code examples
javascriptreactjsjestjsreact-testing-librarytesting-library

Test is failing as the object is incorrect in React Testing Library


Component to test -

import { useState } from "react";

const UserForm = ({ onUserAdd }) => {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    onUserAdd({ name, email });
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Name</label>
        <input value={name} onChange={(e) => setName(e.target.value)} />
      </div>
      <div>
        <label>Email</label>
        <input value={email} onChange={(e) => setEmail(e.target.value)} />
      </div>
      <button onClick={handleSubmit}>Add User</button>
    </form>
  );
};

export default UserForm;

Failing Test Case -

test("it calls onUserAdd when the form is submitted", async () => {
  // BETTER IMPLEMENTATION
  const mock = jest.fn();

  // Render - the component
  render(<UserForm onUserAdd={mock} />);

  // Find 2 inputs (name, email in this case)
  const [nameInput, emailInput] = screen.getAllByRole("textbox");

  // Simulate typing in a name
  await user.click(nameInput);
  await user.keyboard("jane");

  // Simulate typing in a email
  await user.click(emailInput);
  await user.keyboard("abc@abc.com");

  // Find the submit button
  const button = screen.getByRole("button");

  // Simulate clicking the submit button
  await user.click(button);

  // Assertion - make sure 'onUserAdd' gets called with name/email
  expect(mock).toHaveBeenCalled();
  expect(mock).toHaveBeenCalledWith({ name: "jane", email: "abc@abc.com" });
});

Error -

- Expected
+ Received

  Object {
-   "email": "abc@abc.com",
-   "name": "jane",
+   "email": "",
+   "name": "",
  },

Let me know what I am doing wrong here.

Codesandbox Link - https://codesandbox.io/s/morning-microservice-xxh0mw?file=/src/UserForm.test.js

PS - Since I am learning react testing, I used await for every event, let me know if this is a correct approach as well. Thanks.


Solution

  • Generally looks ok but I find CodeSandbox to be really buggy when dealing with react testing library. I replaced your user.clicks and user.keyboards with user.type which I find more human readable and concise to work with and it seems to work:

    test("it calls onUserAdd when the form is submitted", async () => {
      const mock = jest.fn();
    
      // Render - the component
      render(<UserForm onUserAdd={mock} />);
    
      // Find 2 inputs (name, email in this case)
      const [nameInput, emailInput] = screen.getAllByRole("textbox");
    
      // Simulate typing in a name
      await user.type(nameInput, "jane"); // <--here-<<
    
      // Simulate typing in a email
      await user.type(emailInput, "abc@abc.com"); // <--and here-<<
    
      // Find the submit button
      const button = screen.getByRole("button");
    
      // Simulate clicking the submit button
      await user.click(button);
    
      // Assertion - make sure 'onUserAdd' gets called with name/email
      expect(mock).toHaveBeenCalled();
      expect(mock).toHaveBeenCalledWith({ name: "jane", email: "abc@abc.com" });
    });
    

    And yes, you are correct wo await every user action. Although RTL docs recommend to use the new setup const user = userEvent.setup() instead of calling the API directly