10

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.

Vergil C.
  • 1,046
  • 2
  • 15
  • 28

1 Answers1

22

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",
Rafael Tavares
  • 5,678
  • 4
  • 32
  • 48
Lin Du
  • 88,126
  • 95
  • 281
  • 483
  • Thank you very much, this works and saved me from lots of headache. To ask you another question, if I add inside my component a `useLayoutEffect` which will read use the `divref.current.childNodes` to get widths of all the `li` elements, how would I change the tests? I tried it and my in the test it never goes inside the function inside the `useLayoutEffect`. In real-life situation I have this scenario, on first render the component has no refs, on second render i have refs and then i use that metadata for operations inside the component. – Vergil C. May 14 '20 at 16:26
  • @VergilC. You need to ask a new question – Lin Du May 20 '20 at 04:35
  • 4
    I'll getting this typescript error -> `Error:(397, 21) TS2339: Property 'mockReturnValueOnce' does not exist on type '() => RefObject'.` Any ideas how to fix this? – Christian Saiki Jul 28 '20 at 23:27
  • 2
    @ChristianSaiki mock React and its types and then use useRef from that. `const mockedReact = React as jest.Mocked;` and then `mockedReact.useRef.mockReturnValueOnce(mRef);` – Mantas Astra Dec 29 '20 at 06:47
  • 4
    @MantasAstra Im receiving the error `mockedReact.useRef.mockReturnValueOnce is not a function` any idea? – Pedro Vieira Mar 16 '21 at 14:26
  • 1
    This works only on the root of the component, but the ref is still undefined inside useEffect, any way to solve this? – Gabrielle Jan 21 '22 at 22:01
  • Use chaining to mock multiple refs in a single component. `mockedReact.useRef.mockReturnValueOnce(imgRef).mockReturnValueOnce(parentRef).mockReturnValueOnce(captionRef)` Note: order of mocking ref should be same in order as in the component – Saran Raj Jul 06 '22 at 06:49