Search code examples
reactjsjestjsenzymets-jest

React/Jest/Enzyme - Spying on changeHandler fails on .toHaveBeenCalledWith(event) check


Input.tsx :

import React from "react";

export type InputProps = {
  name: string;
  label: string;
  value: any;
  onChange: React.FormEventHandler<HTMLInputElement>;
};

export const Input = ({ name, label, value, onChange }: InputProps) => {
  return (
    <div>
      <label htmlFor={name}>{label}</label>
      <input value={value} name={name} type="text" onChange={onChange} />
    </div>
  );
};

Input.test.tsx :

import React from "react";

import { mount } from "enzyme";
import { act } from "react-dom/test-utils";
import { Input, InputProps } from "./Input";

describe("Input tests", () => {
  test("renders without crashing", () => {
    const handleChange = jest.fn();

    const props: InputProps = {
      name: "login",
      label: "Your Login:",
      value: "",
      onChange: handleChange,
    };

    const wrapper = mount(<Input {...props} />);

    const mockEvent = {
      target: {
        value: "Administrator",
        name: "login",
      },
    } as React.ChangeEvent<HTMLInputElement>;

    act(() => {
      wrapper.find("input").simulate("change", mockEvent);
    });

    expect(handleChange).toHaveBeenCalledTimes(1);
    expect(handleChange).toHaveBeenCalledWith(mockEvent); // fails here
  });
});

The last line with .toHaveBeenCalledWith(mockEvent) fails with the following message:

expect(jest.fn()).toHaveBeenCalledWith(...expected)

- Expected
+ Received

+ SyntheticBaseEvent {
+   "_dispatchInstances": null,
+   "_dispatchListeners": null,
+   "_reactName": "onChange",
+   "_targetInst": FiberNode {
+     "_debugHookTypes": null,

Sample codesandbox here

I have tried:

  1. Changing onChange={onChange} to onChange={e => onChange(e)}
  2. Changing target: {... to currentTarget: {...

But nothing helped. At this point I'm not sure if its because my input is a functional component, or if there is a problem with the versions ? Any help would be highly appreciated!

P.S. The purpose of the test is to learn the syntax rather than achieving meaningful coverage.


Solution

  • simulate merges your mock-event object with a SyntheticEvent object coming from ReactWrapper (see the documentation of simulate), so testing for equality will fail. Instead you can do something like this:

    expect(handleChange).toHaveBeenCalledWith(expect.objectContaining(mockEvent));
    

    This does not work for currentTarget though, as the SyntheticEvent already has a property currentTarget: null, so it will also be null in the merged result. For target it does work. Here's a sandbox that passes the tests.

    It's probably better to just test the handler functions directly and stay away from simulate though, as it's a bit hacky and doesn't really simulate the events. It even seems to be deprecated: https://github.com/enzymejs/enzyme/issues/2173