Adolfo
Adolfo

Reputation: 1465

Testing a component that uses useEffect using Enzyme shallow and not mount

// MyComponent.jsx
const MyComponent = (props) => {
  const { fetchSomeData } = props;

  useEffect(()=> {
    fetchSomeData();
  }, []);

  return (
    // Some other components here
  )
};

// MyComponent.react.test.jsx
...
describe('MyComponent', () => {
  test('useEffect', () => {
    const props = {
      fetchSomeData: jest.fn(),
    };

    const wrapper = shallow(<MyComponent {...props} />);

    // THIS DOES NOT WORK, HOW CAN I FIX IT?
    expect(props.fetchSomeData).toHaveBeenCalled();
  });
});



When running the tests I get:

expect(jest.fn()).toHaveBeenCalled()

Expected mock function to have been called, but it was not called.

The expect fails because shallow does not call useEffect. I cannot use mount because of other issues, need to find a way to make it work using shallow.

Upvotes: 21

Views: 54444

Answers (5)

Guilherme Garnier
Guilherme Garnier

Reputation: 2257

Another solution would be mocking useEffect to call its first argument when called:

jest.mock('react', () => ({
  ...jest.requireActual('react'),
  useEffect: f => f(),
}));

Upvotes: 0

acesmndr
acesmndr

Reputation: 8505

shallow doesn't run effect hooks in React by default (it works in mount though) but you could use jest-react-hooks-shallow to enable the useEffect and useLayoutEffect hooks while shallow mounting in enzyme. Then testing is pretty straightforward and even your test specs will pass. Here is a link to a article where testing the use-effect hook has been clearly tackled with shallow mounting in enzyme https://medium.com/geekculture/testing-useeffect-and-redux-hooks-using-enzyme-4539ae3cb545 So basically with jest-react-hooks-shallow for a component like

const ComponentWithHooks = () => {
  const [text, setText] = useState<>();
  const [buttonClicked, setButtonClicked] = useState<boolean>(false);

  useEffect(() => setText(
    `Button clicked: ${buttonClicked.toString()}`), 
    [buttonClicked]
  );

  return (
    <div>
      <div>{text}</div>
      <button onClick={() => setButtonClicked(true)}>Click me</button>
    </div>
  );
};

you'd write tests like

test('Renders default message and updates it on clicking a button', () => {
  const component = shallow(<App />);

  expect(component.text()).toContain('Button clicked: false');

  component.find('button').simulate('click');

  expect(component.text()).toContain('Button clicked: true');
});

Upvotes: 3

Stephen Paul
Stephen Paul

Reputation: 39025

I'm following this advice and using mount() instead of shallow(). Obviously, that comes with a performance penalty, so mocking of children is advised.

Upvotes: 1

Lenny T
Lenny T

Reputation: 409

Here's a solution from a colleague of mine at CarbonFive: https://blog.carbonfive.com/2019/08/05/shallow-testing-hooks-with-enzyme/

TL;DR: jest.spyOn(React, 'useEffect').mockImplementation(f => f())

Upvotes: 13

ndequeker
ndequeker

Reputation: 7990

useEffect is not supported by Enzyme's shallow rendering. It is on the roadmap (see column 'v16.8+: Hooks') to be fixed for the next version of Enzyme, as mentioned by ljharb

What you're asking is not possible with the current setup. However, a lot of people are struggling with this.

I've solved / worked around this by:

  • not using shallow rendering from Enzyme anymore
  • use the React Testing Library instead of Enzyme
  • mocking out modules via Jest

Here's a summary on how to mock modules, based on Mock Modules from the React docs.

contact.js

import React from "react";
import Map from "./map";

function Contact(props) {
  return (
    <div>
      <p>
        Contact us via [email protected]
      </p>
      <Map center={props.center} />
    </div>
  );
}

contact.test.js

import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";

import Contact from "./contact";
import MockedMap from "./map";

jest.mock("./map", () => {
  return function DummyMap(props) {
    return (
      <p>A dummy map.</p>
    );
  };
});

it("should render contact information", () => {
  const center = { lat: 0, long: 0 };
  act(() => {
    render(
      <Contact
        name="Joni Baez"
        email="[email protected]"
        site="http://test.com"
        center={center}
      />,
      container
    );
  });
});

Useful resources:

Upvotes: 13

Related Questions