React testing library - not able to fire click event

Viewed 43

I have a <Select/> like below.
enter image description here

App.tsx

import React, { useState, ChangeEvent } from "react";

import MySelect from "./MySelect";

export default function App() {
  const [countryCode, setCountryCode] = useState<string>("AL");

  const onSelectCountryCode = (event: ChangeEvent<HTMLSelectElement>): void => {
    setCountryCode(event.target.value);
  };

  return <MySelect value={countryCode} onChange={onSelectCountryCode} />;
}

MySelect.tsx

import React, { ChangeEvent } from "react";
import { Select } from "@chakra-ui/react";

const COUNTRY_CODE = [
  {
    name: "Afghanistan",
    dial_code: "93",
    code: "AF"
  },
  {
    name: "Aland Islands",
    dial_code: "358",
    code: "AX"
  },
  {
    name: "Albania",
    dial_code: "355",
    code: "AL"
  }
];

interface MySelectProp {
  value: string;
  onChange: (event: ChangeEvent<HTMLSelectElement>) => void;
}

export default function MySelect({ value, onChange }: MySelectProp) {
  return (
    <Select onChange={onChange} value={value} data-testid="dial-code-select">
      {COUNTRY_CODE.map((option) => (
        <option
          key={option.code}
          value={option.code}
          data-testid="dial-code-select-option"
        >
          {`${option.dial_code} (${option.code})`}
        </option>
      ))}
    </Select>
  );
}

I have created a test case, which simulate users clicking another option in the Select.

Select.test.tsx

import React from "react";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";

import MySelect from "./MySelect";

const mockOnChange = jest.fn();

describe("MySelect", () => {
  it("renders correctly - when changing options", async () => {
    const mockProp = {
      value: "AL",
      onChange: mockOnChange
    };
    render(<MySelect {...mockProp} />);

    expect(mockOnChange).toHaveBeenCalledTimes(0);

    await waitFor(() => screen.getByText("93 (AF)"));

    fireEvent.click(screen.getByText("93 (AF)"));

    // FAIL
    // ● DialCodeSelect › renders correctly - when changing options
    // expect(jest.fn()).toHaveBeenCalledTimes(expected)
    // Expected number of calls: 1
    // Received number of calls: 0
    expect(mockOnChange).toHaveBeenCalledTimes(1);
    expect(mockOnChange).toHaveBeenCalledWith({
      label: "AF",
      value: "AF"
    });
  });
});

The error throws after running the test

     FAIL
     ● DialCodeSelect › renders correctly - when changing options
     expect(jest.fn()).toHaveBeenCalledTimes(expected)
     Expected number of calls: 1
     Received number of calls: 0

Codesandbox
https://codesandbox.io/s/react-typescript-forked-ofs5hw

How to fix that?

1 Answers

You need to make sure the select changed, searching for a text (like getByText("93 (AF)")) will be valid as the text is in the html body -- but not selected in the component. also notice the changes in the json and the role added to the option elements.

const COUNTRY_CODE = [
  {
    name: "Dreamland", // to ensure it starts with something random
    dial_code: "90",
    code: "DL"},
  {
    name: "Afghanistan",
    dial_code: "93",
    code: "AF"
  },
  ....
];

interface MySelectProp {
  value?: string; // optional does it
  onChange: (event: ChangeEvent<HTMLSelectElement>) => void;
}

export default function MySelect({ value, onChange }: MySelectProp) {
  return (
    <Select onChange={onChange} value={value} data-testid="dial-code-select">
      {COUNTRY_CODE.map((option) => (
        <option key={option.code} value={option.code} role="option">
          {`${option.dial_code} (${option.code})`}
        </option>
      ))}
    </Select>
  );
}

on the test body

import userEvent from "@testing-library/user-event";
...

const mockProp = {
  onChange: mockOnChange 
  // for some reason, it re-renders and it changes back to the initial
  // option, that's why i made value optional in the interface
};
render(<MySelect {...mockProp} />);
expect(mockOnChange).toHaveBeenCalledTimes(0);

const select = screen.getByTestId("dial-code-select"); // get the Select itself
expect(select.value).toBe("DL"); // check initial selected option

userEvent.selectOptions(select, "AF"); // fire "user" event rather than "browser" event
expect(screen.getByRole("option", { name: "93 (AF)" }).selected).toBe(true);

expect(mockOnChange).toHaveBeenCalledTimes(1);
expect(select.value).toBe("AF");

https://codesandbox.io/s/react-typescript-forked-z0i9xt?file=/src/MySelect.spec.tsx

Related