What is a custom hook? π€
Custom hooks in ReactJS are reusable pieces of code that encapsulate logic and state management. As developers, we need to ensure that these hooks work as intended and do not have any unintended side effects. This is where testing custom hooks becomes crucial.
In this article, we will explore how to test custom hooks in ReactJS using the useClipboard
hook as an example.
What is the useClipboard hook? π€
The useClipboard
hook is a simple hook that allows you to copy text to the clipboard. It takes three parameters: the text to copy, the delay (in ms) to switch back to the initial state once copied, and a callback function to execute when the content is copied to the clipboard.
You can read more about how it uses the clipboard API to copy the text here.
import { useCallback, useEffect, useState } from 'react';
const useClipboard = (
value: string,
timeout = 1500,
callBack?: () => void
) => {
const [hasCopied, setHasCopied] = useState(false);
const [valueState, setValueState] = useState(value);
const handleCopy = useCallback(async () => {
try {
await navigator.clipboard.writeText(valueState);
setHasCopied(true);
callBack && callBack();
} catch (error) {
setHasCopied(false);
}
}, [valueState]);
useEffect(() => setValueState(value), [value]);
useEffect(() => {
let timeoutId: number | null = null;
if (hasCopied) {
timeoutId = Number(
setTimeout(() => {
setHasCopied(false);
}, timeout)
);
}
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
};
}, [timeout, hasCopied]);
return {
onCopyToClipBoard: handleCopy,
hasCopied,
};
};
Test the useClipboard hook π§ͺ
We first need to create a test file to begin testing the hook. Let's call it useClipboard.test.tsx
. In this file, we'll import the useClipboard
hook and any necessary dependencies.
import { renderHook, act } from '@testing-library/react-hooks';
import { useClipboard } from './useClipboard';
What is renderHook
? π‘
renderHook
is a function provided by the @testing-library/react-hooks
library that allows you to test React hooks in isolation.
It provides a testing environment for React hooks by rendering them in a mock component.
It allows you to test the hook's behavior and state changes in isolation, without the need for a full React component.
It returns an object with a
result
property that holds the current state of the hook.
Alright, now we know what is renderHook function and how it works.
Next, we'll write our first test to check if the hook returns an object with two properties: onCopyToClipBoard
and hasCopied
.
Should return an object with two properties π§ͺ
it('should return an object with onCopyToClipBoard and hasCopied properties', () => {
const { result } = renderHook(() => useClipboard('test'));
expect(result.current).toHaveProperty('onCopyToClipBoard');
expect(result.current).toHaveProperty('hasCopied');
});
In this test, we render the hook using the renderHook
function from @testing-library/react-hooks
. We pass in the text we want to copy as the first argument. Then we use the expect
function to check if the hook returns an object with the two properties we expect.
Next, we'll write a test to check if the onCopyToClipBoard
function updates the hasCopied
state.
Should update the state π§ͺ
it('should update hasCopied state when content is copied to clipboard', async () => {
const { result } = renderHook(() => useClipboard('test'));
await act(async () => {
result.current.onCopyToClipBoard();
});
expect(result.current.hasCopied).toBe(true);
});
In this test, we call the onCopyToClipBoard
function and use the await
keyword to wait for the function to finish executing. We then check if the hasCopied
the state is set to true.
Finally, we'll write a test to check if the hasCopied
state resets after the timeout.
Should reset the state after a timeout π§ͺ
it('should reset hasCopied state after timeout', async () => {
jest.useFakeTimers();
const { result, rerender } = renderHook(() =>
useClipboard('test', 1500)
);
await act(async () => {
result.current.onCopyToClipBoard();
});
expect(result.current.hasCopied).toBe(true);
act(() => {
jest.advanceTimersByTime(1500);
});
rerender();
expect(result.current.hasCopied).toBe(false);
jest.useRealTimers();
});
In this test, we use the jest.useFakeTimers()
function to fake the timer. We render the hook with a delay of 1500ms and call the onCopyToClipBoard
function. We then advance the timer by 1500ms and rerender the hook. Finally, we check if the hasCopied
state is set to false.
Conclusion β¨
Testing custom hooks in ReactJS is crucial for ensuring the functionality of our components. While it may be challenging at times, with the right approach and tools like @testing-library/react-hooks
, we can write effective tests and ensure our hooks work as intended.
And thatβs it for this topic. Thank you for reading.
If you found this article useful, please consider liking and sharing it with others. If you have any questions, feel free to comment, and I will do my best to respond.