Skip to content

handleChange works unexpectedly when input is inside of a component being tested #713

Closed
@nooruddin

Description

@nooruddin

I am trying to figure out why following line of code would always work no matter what number I put.
waitForElement(() => expect(onChange).toHaveBeenCalledTimes(3));

I am trying to test that input has been called x number of times however input is controlled within the component itself. No matter what number provide it will always pass the test.
complete-react-testing.zip

With fireEvent, I expect it to be
waitForElement(() => expect(onChange).toHaveBeenCalledTimes(1));

With userEvent, I expect it to be
waitForElement(() => expect(onChange).toHaveBeenCalledTimes(5));

Here is my Component:

import React, { useState } from "react";
import axios from "axios";
import "./Pokemon.css";

const getPokemonByColor = (color) =>
  `https://pokeapi.co/api/v2/pokemon-color/${color}/`;

const Pokemon = () => {
  const [pokemons, setPokemons] = useState([]);
  const [error, setError] = useState(null);
  const [search, setSearch] = useState("");
  const handleFetch = async (event) => {
    event.preventDefault();
    let result;

    try {
      result = await axios.get(getPokemonByColor(search));
      setPokemons(result.data.pokemon_species.slice(0, 5));
    } catch (error) {
      setError(error);
    }
  };

  const handleChange = (event) => {
    setSearch(event.target.value);
  };

  return (
    <div>
      {error && <span>Something went wrong ...</span>}
      <form onSubmit={handleFetch}>
        <div>
          <input
            id="search"
            type="text"
            value={search}
            onChange={handleChange}
            placeholder="Pokemon Color"
            className="search"
          />
        </div>
        <button className="search-button" type="submit" data-testid="button">
          Fetch Pokemons
        </button>
      </form>
      <ul className="pokemons">
        {pokemons.length > 0 &&
          pokemons.map((pokemon) => (
            <li className="pokemon-item" key={pokemon.name}>
              <a className="pokemon-link" href={pokemon.url}>
                {pokemon.name}
              </a>
            </li>
          ))}
      </ul>
    </div>
  );
};

export default Pokemon;

Here are my tests:

import React from "react";
import axios from "axios";
import {
  render,
  screen,
  waitForElement,
  fireEvent,
  cleanup,
} from "@testing-library/react";
import userEvent from "@testing-library/user-event";

import Pokemon from "./Pokemon";

jest.mock("axios");

afterEach(cleanup);
describe("search input tests", () => {
  test("calls the onChange callback handler", () => {
    const onChange = jest.fn();

    const { getByRole } = render(<Pokemon />);
    const input = getByRole("textbox");
    expect(input.value).toBe("");
    fireEvent.change(input, {
      target: { value: "black" },
    });
    expect(input.value).toBe("black");
    waitForElement(() => expect(onChange).toHaveBeenCalledTimes(3));
  });

  test("calls the onChange callback handler", async () => {
    const onChange = jest.fn();

    const { getByRole } = render(<Pokemon />);

    const input = getByRole("textbox");
    expect(input.value).toBe("");
    await userEvent.type(input, {
      target: { value: "black" },
    });
    waitForElement(() => expect(input.value).toBe("black"));
    waitForElement(() => expect(onChange).toHaveBeenCalledTimes(7));
  });
});

describe("api tests", () => {
  test("fetches pokemons from an API and display them", async () => {
    const pokemons = [
      {
        name: "snorlax",
        url: "https://pokeapi.co/api/v2/pokemon-species/143/",
      },
      {
        name: "murkrow",
        url: "https://pokeapi.co/api/v2/pokemon-species/198/",
      },
      {
        name: "unown",
        url: "https://pokeapi.co/api/v2/pokemon-species/201/",
      },
      {
        name: "sneasel",
        url: "https://pokeapi.co/api/v2/pokemon-species/215/",
      },
    ];

    axios.get.mockImplementationOnce(() =>
      Promise.resolve({ data: { pokemon_species: pokemons } })
    );

    render(<Pokemon />);
    // screen.debug();
    userEvent.click(screen.getByRole("button"));
    expect(await screen.findAllByRole("listitem")).toHaveLength(4);
    // screen.debug();
  });

  test("fetches stories from an API and fails", async () => {
    axios.get.mockImplementationOnce(() => Promise.reject(new Error()));

    render(<Pokemon />);

    userEvent.click(screen.getByRole("button"));

    const message = await screen.findByText(/Something went wrong/);

    expect(message).toBeInTheDocument();
  });
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions