6

I have a test case where I need to mock useRef inorder to return a mock current value. I tried jest.mock but it returns an HTMLDivElement instead.

code:

   const ref = useRef<HTMLDivElement | null>(null);

test:

  jest.mock('react', () => {
     const originReact = jest.requireActual('react');
       return {
          ...originReact,
          useRef: jest.fn(),
       };
  });



  React.useRef.mockReturnValue({ current: {offsetWith: 100} }); 

Mock returns

[ { type: 'return', value: { current: [HTMLDivElement] } } ]
2
  • 4
    Don't mock any of React's API, you don't own that. Test the behaviour of the component, not the implementation. Commented Feb 23, 2021 at 12:15
  • 1
    i see. is there a way to test if the current ref value like offsetWidth changes. thanks Commented Feb 23, 2021 at 12:32

1 Answer 1

8

@jonrsharpe's suggestion is the general principle of component testing, but your question is a special case, involving some properties and methods of the DOM, such as offsetWidth and getBoundingClientRect() methods. The runtime environment of jsdom cannot return and the rendering engine under the browser runtime environment The returned result causes the offsetWidth and the property values returned by the getBoundingClientRect() method to always be 0.

So here we need mock ref.

The mock ref here is also special. In addition to using jest.mock() for partial mocking, the ref.current property will be assigned by react after the component is mounted. In order to intercept this operation, we need to create a mock ref object with a current object property, use the Object.defineProperty() method to define getter and setter for this ref.current property, and intercept property assignment.

jest.spyOn method takes an optional third argument of accessType that can be either 'get' or 'set', which proves to be useful when you want to spy on a getter or a setter, respectively.

E.g.

index.tsx:

import React, { useEffect, useRef } from 'react';

export default function App() {
  const ref = useRef<HTMLDivElement>(null);
  useEffect(() => {
    console.log(ref.current?.offsetWidth);
  }, [ref]);
  return <div ref={ref}>app</div>;
}

index.test.tsx:

import { render } from '@testing-library/react';
import React, { useRef } from 'react';
import { mocked } from 'ts-jest/utils';
import App from './';

jest.mock('react', () => {
  return {
    ...jest.requireActual<typeof React>('react'),
    useRef: jest.fn(),
  };
});

const useMockRef = mocked(useRef);

describe('66332902', () => {
  afterEach(() => {
    jest.clearAllMocks();
  });
  afterAll(() => {
    jest.resetAllMocks();
  });
  test('should mock ref and offsetWidth', () => {
    const ref = { current: {} };
    Object.defineProperty(ref, 'current', {
      set(_current) {
        if (_current) {
          jest.spyOn(_current, 'offsetWidth', 'get').mockReturnValueOnce(100);
        }
        this._current = _current;
      },
      get() {
        return this._current;
      },
    });
    useMockRef.mockReturnValueOnce(ref);
    render(<App />);
  });
});

test result: (see the logs)

 PASS  examples/66332902/index.test.tsx (9.518 s)
  66332902
    ✓ should mock ref and offsetWidth (39 ms)

  console.log
    100

      at examples/66332902/index.tsx:6:13

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        10.106 s
Sign up to request clarification or add additional context in comments.

3 Comments

This does not work, if inside <App> other components call useRef. Any idea how to solve this @slideshowp2?
@philk - i'm also searching for a way to mock ref properties for a given component... not the entire useRef hook!
please have a look at this question : stackoverflow.com/questions/73972445/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.