How to Test Custom Hook in ReactJS
Tags: react, testing, javascript
Published: 2023-04-20 07:58:39

## 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](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API).

```typescript
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.

```javascript
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 🧪

```javascript
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 🧪

```javascript
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 🧪

```javascript
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.

## **Resource 💡**

* [Learn more about render hook](https://testing-library.com/docs/react-testing-library/api/#renderhook)
    
* [Gist of useClipboard hook](https://gist.github.com/Sachin-chaurasiya/56154e66a68f077bc1d858ca4d3c92da)
    

## **Connect with me 👋**

* [**LinkedIn**](https://www.linkedin.com/in/sachin-chaurasiya)
    
* [**Twitter**](https://twitter.com/sachindotcom)