Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I have a component that uses useContext and then its output is dependent on the value in the context. A simple example:

import React, { useContext } from 'react';  const MyComponent = () => {   const name = useContext(NameContext);    return <div>{name}</div>; }; 

When testing this component with the shallow renderer from react and jest snapshots. How can I change the value of NameContext?

like image 358
bensampaio Avatar asked Feb 14 '19 13:02

bensampaio


Video Answer


1 Answers

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"); }); 
like image 147
Retsam Avatar answered Sep 22 '22 16:09

Retsam