reactjsreact-hooksjestjsmockingenzyme

Mock React useRef or a function inside a functional component with enzyme and jest?


Codesanbox link - includes working component Child2.js and working test Child2.test.js

Child2.js

import React, { useRef } from "react";

export default function Child2() {
  const divRef = useRef();

  function getDivWidth() {
    if (divRef.current) {
      console.log(divRef.current);
    }
    return divRef.current ? divRef.current.offsetWidth : "";
  }

  function getDivText() {
    const divWidth = getDivWidth();

    if (divWidth) {
      if (divWidth > 100) {
        return "ABC";
      }
      return "123";
    }

    return "123";
  }

  return (
    <>
      <div id="myDiv" ref={divRef}>
        {getDivText()}
      </div>
      <p>Div width is: {getDivWidth()}</p>
    </>
  );
}

Child2.test.js

import React from "react";
import Enzyme, { shallow } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import Child2 from "../src/Child2";

Enzyme.configure({ adapter: new Adapter() });

it("div text is ABC when div width is more then 100 ", () => {
  const wrapper = shallow(<Child2 />);
  expect(wrapper.find("#myDiv").exists()).toBe(true);
  expect(wrapper.find("#myDiv").text()).toBe("ABC");
});

it("div text is 123 when div width is less then 100 ", () => {
  const wrapper = shallow(<Child2 />);
  expect(wrapper.find("#myDiv").exists()).toBe(true);
  expect(wrapper.find("#myDiv").text()).toBe("123");
});

When i run the tests, obvisouly the offsetWidth of the div is 0, hence i need to find a way to mock the useRef to return a div element with width or mock the getDivWidth function to return a desired number for the width.

How could I achieve that? I have searched for a solution, but I am stuck. There are some examples with class components or using typescript which I have not managed to use.


Solution

  • You can use jest.mock(moduleName, factory, options) and jest.requireActual(moduleName) APIs to mock useRef hook except others. Which means other functions and methods of react are still original version.

    E.g.

    index.jsx:

    import React, { useRef } from 'react';
    
    export default function Child2() {
      const divRef = useRef();
    
      function getDivWidth() {
        if (divRef.current) {
          console.log(divRef.current);
        }
        return divRef.current ? divRef.current.offsetWidth : '';
      }
    
      function getDivText() {
        const divWidth = getDivWidth();
    
        if (divWidth) {
          if (divWidth > 100) {
            return 'ABC';
          }
          return '123';
        }
    
        return '123';
      }
    
      return (
        <>
          <div id="myDiv" ref={divRef}>
            {getDivText()}
          </div>
          <p>Div width is: {getDivWidth()}</p>
        </>
      );
    }
    

    index.test.jsx:

    import React, { useRef } from 'react';
    import { shallow } from 'enzyme';
    import Child2 from './';
    
    jest.mock('react', () => {
      const originReact = jest.requireActual('react');
      const mUseRef = jest.fn();
      return {
        ...originReact,
        useRef: mUseRef,
      };
    });
    
    describe('61782695', () => {
      it('should pass', () => {
        const mRef = { current: { offsetWidth: 100 } };
        useRef.mockReturnValueOnce(mRef);
        const wrapper = shallow(<Child2></Child2>);
        expect(wrapper.find('#myDiv').text()).toBe('123');
        expect(wrapper.find('p').text()).toBe('Div width is: 100');
      });
    
      it('should pass - 2', () => {
        const mRef = { current: { offsetWidth: 300 } };
        useRef.mockReturnValueOnce(mRef);
        const wrapper = shallow(<Child2></Child2>);
        expect(wrapper.find('#myDiv').text()).toBe('ABC');
        expect(wrapper.find('p').text()).toBe('Div width is: 300');
      });
    
      it('should pass - 3', () => {
        const mRef = {};
        useRef.mockReturnValueOnce(mRef);
        const wrapper = shallow(<Child2></Child2>);
        expect(wrapper.find('#myDiv').text()).toBe('123');
        expect(wrapper.find('p').text()).toBe('Div width is: ');
      });
    });
    

    Unit test results with 100% coverage:

     PASS  stackoverflow/61782695/index.test.jsx (9.755s)
      61782695
        ✓ should pass (111ms)
        ✓ should pass - 2 (15ms)
        ✓ should pass - 3 (1ms)
    
      console.log
        { offsetWidth: 100 }
    
          at getDivWidth (stackoverflow/61782695/index.jsx:8:15)
    
      console.log
        { offsetWidth: 100 }
    
          at getDivWidth (stackoverflow/61782695/index.jsx:8:15)
    
      console.log
        { offsetWidth: 300 }
    
          at getDivWidth (stackoverflow/61782695/index.jsx:8:15)
    
      console.log
        { offsetWidth: 300 }
    
          at getDivWidth (stackoverflow/61782695/index.jsx:8:15)
    
    -----------|---------|----------|---------|---------|-------------------
    File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    -----------|---------|----------|---------|---------|-------------------
    All files  |     100 |      100 |     100 |     100 |                   
     index.jsx |     100 |      100 |     100 |     100 |                   
    -----------|---------|----------|---------|---------|-------------------
    Test Suites: 1 passed, 1 total
    Tests:       3 passed, 3 total
    Snapshots:   0 total
    Time:        10.885s
    

    package versions:

    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "enzyme": "^3.11.0",
    "enzyme-adapter-react-16": "^1.15.2",
    "jest": "^25.5.4",
    "jest-environment-enzyme": "^7.1.2",
    "jest-enzyme": "^7.1.2",