Mocking refs in React function component
Solution 1
Here is my unit test strategy, use jest.spyOn
method spy on the useRef
hook.
index.tsx
:
import React from 'react';
export const Comp = ({ onHandle }: any) => {
const ref = React.useRef(null);
const handleClick = () => {
if (!ref.current) return;
onHandle();
};
return (
<button ref={ref} onClick={handleClick}>
test
</button>
);
};
index.spec.tsx
:
import React from 'react';
import { shallow } from 'enzyme';
import { Comp } from './';
describe('Comp', () => {
afterEach(() => {
jest.restoreAllMocks();
});
it('should do nothing if ref does not exist', () => {
const useRefSpy = jest.spyOn(React, 'useRef').mockReturnValueOnce({ current: null });
const component = shallow(<Comp></Comp>);
component.find('button').simulate('click');
expect(useRefSpy).toBeCalledWith(null);
});
it('should handle click', () => {
const useRefSpy = jest.spyOn(React, 'useRef').mockReturnValueOnce({ current: document.createElement('button') });
const mock = jest.fn();
const component = shallow(<Comp onHandle={mock}></Comp>);
component.find('button').simulate('click');
expect(useRefSpy).toBeCalledWith(null);
expect(mock).toBeCalledTimes(1);
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/57805917/index.spec.tsx
Comp
✓ should do nothing if ref does not exist (16ms)
✓ should handle click (3ms)
-----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.tsx | 100 | 100 | 100 | 100 | |
-----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 4.787s, estimated 11s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/57805917
Solution 2
The solution from slideshowp2 didn't work for me, so ended up using a different approach:
Worked around it by
- Introduce a useRef optional prop and by default use react's one
import React, { useRef as defaultUseRef } from 'react'
const component = ({ useRef = defaultUseRef }) => {
const ref = useRef(null)
return <RefComponent ref={ref} />
}
- in test mock useRef
const mockUseRef = (obj: any) => () => Object.defineProperty({}, 'current', {
get: () => obj,
set: () => {}
})
// in your test
...
const useRef = mockUseRef({ refFunction: jest.fn() })
render(
<ScanBarcodeView onScan={handleScan} useRef={useRef} />,
)
...
Solution 3
If you use ref
in nested hooks of a component and you always need a certain current
value, not just to the first renderer. You can use the following option in tests:
const reference = { current: null };
Object.defineProperty(reference, "current", {
get: jest.fn(() => null),
set: jest.fn(() => null),
});
const useReferenceSpy = jest.spyOn(React, "useRef").mockReturnValue(reference);
and don't forget to write useRef
in the component like below
const ref = React.useRef(null)
Shadowlauch
Updated on November 02, 2021Comments
-
Shadowlauch over 2 years
I have React function component that has a ref on one of its children. The ref is created via
useRef
.I want to test the component with the shallow renderer. I have to somehow mock the ref to test the rest of the functionality.
I can't seem to find any way to get to this ref and mock it. Things I have tried
Accessing it via the childs property. React does not like that, since ref is not really a props
Mocking useRef. I tried multiple ways and could only get it to work with a spy when my implementation used
React.useRef
I can't see any other way to get to the ref to mock it. Do I have to use mount in this case?
I can't post the real scenario, but I have constructed a small example
it('should test', () => { const mock = jest.fn(); const component = shallow(<Comp onHandle={mock}/>); // @ts-ignore component.find('button').invoke('onClick')(); expect(mock).toHaveBeenCalled(); }); const Comp = ({onHandle}: any) => { const ref = useRef(null); const handleClick = () => { if (!ref.current) return; onHandle(); }; return (<button ref={ref} onClick={handleClick}>test</button>); };
-
Jhon Mike almost 5 yearsSubmits the code structure and test you tried to create for ease.
-
apokryfos almost 5 yearsThere's this issue which seems to say that you can't do it with shallow rendering
-
Shadowlauch almost 5 years@JhonMike I have added a small example
-
Rikin almost 5 years@Shadowlauch use
mount
instead asshallow
dont support refs -
skyboyer almost 5 yearsavowed
useRef
is not replacement forReact.createRef
-
Shadowlauch almost 5 years@skyboyer what are you trying to say? In a function component I have to use the hook, otherwise a new ref gets created every render
-
skyboyer almost 5 yearshow do you want to use
ref
? if using in component's logic or event handler? or do you want to pass it outside? anyway you need to initialize ref withReact.createRef
regardless if it's creating on each render or saving between renders by usinguseRef
-
Shadowlauch over 4 yearsThe issue I have with this solution, it forces me to use
React.useRef
. But I guess this is the only way with shallow. -
Julian Torregrosa over 4 years@Shadowlauch your comment is the key
-
Drazen Bjelovuk over 4 yearsWhat if the component has multiple
useRef
s? -
slideshowp2 about 4 years@Shadowlauch Check this answer: stackoverflow.com/a/61789113/6463558
-
slideshowp2 about 4 yearsTry this answer: stackoverflow.com/a/61789113/6463558
-
Zargold over 3 yearsit does not seem to actually mock the ref to return a
{current: null}
(at least not when using mount). -
Tom about 3 yearsThis didn't work for me , this did stackoverflow.com/a/61789113/168012
-
nima about 2 yearsit's working well, thanks