Test a hook throwing errors in React 18 with renderHook from Testing Library 🚨🦑
Tags: react, testing, javascript, coding
Published: 2023-05-25 20:09:12

When writing React applications, you likely want to use [Testing Library](https://testing-library.com). It's a tool specifically designed to test UI components from a user's perspective, encouraging developers to write tests that simulate real user interactions. It comes with a great [React integration](https://testing-library.com/docs/react-testing-library/intro).

The `renderHook` function is a utility that allows you to test custom hooks. It provides a way to render a custom hook in a testing environment and access the values it returns. `renderHook` sets up the necessary infrastructure to execute the hook and provides an API to interact with its state and effects, enabling comprehensive testing of the hook's behavior and functionality. It ensures that hooks are tested in isolation, making it easier to write effective unit tests for hooks without the need for rendering a full React component.


## Why a hook might throw an error

Oftentimes, you want your custom hook to explicitly throw an error if something is wrong. Possible scenarios could be:

- A hook that relies on certain function arguments and throws if they are not provided correctly.
- A hook that reads the value from a React context and throws if the context provider is not present. 
- A hook that depends on an external API and throws if it receives a negative response.

In such scenarios, you want to make sure the behavior works as intended, and you can use `renderHook` for this purpose.

First, let's take a look at a custom hook that throws an error. It's an easy one, because it always throws.

```javascript
function useSomething(){
	throw new Error("something failed");
}
```

The following examples in this article are written for [Jest](https://jestjs.io), but the same concept applies to other testing frameworks like [Mocha](https://mochajs.org) or [Cypress](https://cypress.io/).

## The old way before React 18 ❌

Before the release of React 18 `renderHook` was provided in a separated package called [@testing-library/react-hooks](https://github.com/testing-library/react-hooks-testing-library). To ensure a hook throws an error, they integrated a wrapper that catches occurring errors and provides them under `result.error`. 

This is how you would have tested if a hook throws an error:

```javascript
test('should throw an error', () => {
	const { result } = renderHook(() => useSomething());
	expect(result.error).toBe(Error('something failed'));
})	
```

You call the `renderHook` function and pass a callback function that calls the hook function. `renderHook` returns an object included the `result` of the call. In case an error was thrown, you can read `result.error`. You can make an assertion in your unit test and compare it to an expected error message.

## The new way with React 18 ✅

After the release of React 18 the [authors of Testing Library decided](https://github.com/testing-library/react-hooks-testing-library#details) to integrate the `renderHook` utility directly into `@testing-library/react` instead of maintaining a separated package for this API.

As you can read in the [pull request for this change](https://github.com/testing-library/react-testing-library/pull/991), testing errors will no longer be solved. This means you need a different approach to ensure a custom hook throws an error.

This is how you can test for thrown errors now:

```javascript
test('should throw an error', () => { 
	expect(() => {  
		renderHook(() => useSomething());  
	}).toThrow('something failed');  
});
```

This is pretty straightforward. It doesn't rely on any additional implementation provided by Testing Library to catch the thrown error and deal with it. Instead, the error simply escalates, and you can handle it yourself, like here with the [`.toThrow()` matcher](https://jestjs.io/docs/expect#tothrowerror).

If you feel like passing an arrow function to `expect` looks a bit funny, this slightly more verbose approach also works:

```javascript
test('should throw an error', () => { 
  try {
    renderHook(() => useSomething());
  }
  catche(error){
    expect(error).toBe('something failed')
  }
});
```

There are quite a few other things that changed for Testing Library with the release of React 18. You can check out the [migration guide](https://github.com/testing-library/react-hooks-testing-library/blob/chore/migration-guide/MIGRATION_GUIDE.md) for further information.