dmbaranov
dmbaranov

Reputation: 191

Test that React prop method has been called with Jest

I have an Input component, which accepts a prop method and calls it when the user types something in. Code itself works as expected, but for some reasons, test fails. It thinks that prop method wasn't called. Why is it happening? For testing purposes, I use Jest and react-testing-library.

And second question. In real application, my idea is to test parameters that were passed to that prop method. Is it considered to be an implementation testing (I know that I should test it)?

Input.js

export default function Input({ onChange }) {
  return <input onChange={onChange} />;
}

Test

import React from "react";
import { render, act, cleanup, fireEvent } from "react-testing-library";
import Input from "./input";

describe("Input tests", () => {
  afterEach(cleanup);

  it("Should call prop function", () => {
    const onChange = jest.fn();
    const { getByTestId } = render(<Input onChange={onChange} />);
    const input = getByTestId("input");

    act(() => {
      fireEvent.change(input, { target: { value: "Q" } });
    });

    expect(onChange).toHaveBeenCalled();
  });
});

https://codesandbox.io/s/y229669nvx

Upvotes: 4

Views: 10459

Answers (2)

Gio Polvara
Gio Polvara

Reputation: 26978

The reason why your test doesn't work is that you're using getByTestId to find your element. getByTestId looks for a DOM node that has a data-testid attribute.

In order to make your test pass, you have various options.

You could add a data-testid to your input: <input data-testid="input" onChange={onChange} />. This would work, however, it's better to avoid test ids whenever you can.

In a real application, your input would be rendered with a label, we can take advantage of that:

const { getByLabelText } = render(
  <label>
    My input
    <Input onChange={onChange} />
  </label>
)
const input = getByLabelText('My input')

Another solution is to use container which is one one of the values returned by render. It's a DOM node—like everything else in RTL—so you can use the usual DOM APIs:

const { container } = render(<Input onChange={onChange} />)
// Any of these would work
const input = container.firstChild
const input = container.querySelector('input')

As a side note, I agree that RTL tests seem more complicated if compared to Enzyme. There's a good reason for it. RTL pushes you to test your application as if it were a black box. This is a bit harder to do in the beginning but ultimately leads to better tests.

Enzyme, on the other hand, mocks most things by default and allows you to interact with your components implementation. This, in my experience, looks easier in the beginning but will produce brittle tests.

I encourage you to join the spectrum channel if you need help getting started.

Upvotes: 0

Matt Carlotta
Matt Carlotta

Reputation: 19762

After reading this, it looks like it's by design to not assert against events handlers. Although it appears to work in React 16.5, however, using 16.8.x fails. I'd suggest moving to enzyme if you want to test such features.

Testing with react-testing-library fails (however, as you'll notice, when running the test, the input's value will actually change): https://codesandbox.io/s/n3rvy891n4

Testing with enzyme succeeds: https://codesandbox.io/s/lx34ny41nl

Upvotes: 1

Related Questions