How to test a react component that is dependent on useContext hook?

In general, using hooks shouldn’t change testing strategy much. The bigger issue here actually isn’t the hook, but the use of context, which complicates things a bit.

There’s a number of ways to make this work, but only approach I’ve found that works with 'react-test-renderer/shallow' is to inject a mock hook:

import ShallowRenderer from 'react-test-renderer/shallow';

let realUseContext;
let useContextMock;
// Setup mock
beforeEach(() => {
    realUseContext = React.useContext;
    useContextMock = React.useContext = jest.fn();
});
// Cleanup mock
afterEach(() => {
    React.useContext = realUseContext;
});

test("mock hook", () => {
    useContextMock.mockReturnValue("Test Value");
    const element = new ShallowRenderer().render(
        <MyComponent />
    );
    expect(element.props.children).toBe('Test Value');
});

This is a bit dirty, though, and implementation-specific, so if you’re able to compromise on the use of the shallow renderer, there’s a few other options available:

Non-shallow render

If you’re not shallow rendering, you can just wrap the component in a context provider to inject the value you want:

import TestRenderer from 'react-test-renderer';

test("non-shallow render", () => {
    const element = new TestRenderer.create(
        <NameContext.Provider value="Provided Value">
            <MyComponent />
        </NameContext.Provider>
    );
    expect(element.root.findByType("div").children).toEqual(['Provided Value']);
});

(Disclaimer: this should work, but when I test it, I’m hitting an error which I think is an issue in my setup)

Shallow render with Enzyme and Dive

As @skyboyer commented, enzyme’s shallow renderer supports .dive allowing you to deeply renderer a part of an otherwise shallow rendered component:

import { shallow } from "./enzyme";

test("enzyme dive", () => {
    const TestComponent = () => (
        <NameContext.Provider value="Provided Value">
            <MyComponent />
        </NameContext.Provider>
    );
    const element = shallow(<TestComponent />);
    expect(element.find(MyComponent).dive().text()).toBe("Provided Value");
});

Use ReactDOM

Finally, the Hooks FAQ has an example of testing hooks with ReactDOM, which works as well. Naturally, using ReactDOM means this is also a deep render, not shallow.

let container;
beforeEach(() => {
    container = document.createElement('div');
    document.body.appendChild(container);
});

afterEach(() => {
    document.body.removeChild(container);
    container = null;
});

test("with ReactDOM", () => {
    act(() => {
        ReactDOM.render((
            <NameContext.Provider value="Provided Value">
                <MyComponent />
            </NameContext.Provider>
        ), container);
    });

    expect(container.textContent).toBe("Provided Value");
});

Leave a Comment