DEV Community

Cover image for How to Test Custom Hook in ReactJS
Sachin Chaurasiya
Sachin Chaurasiya

Posted on • Originally published at blogs.deuexsolutions.com

How to Test Custom Hook in ReactJS

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,
  };
};
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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');
});
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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();
});
Enter fullscreen mode Exit fullscreen mode

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.

Resource 💡

Connect with me 👋

Top comments (0)