DEV Community

Alex Claes
Alex Claes

Posted on • Edited on

Test a hook throwing errors in React 18 with renderHook from Testing Library πŸš¨πŸ¦‘

When writing React applications, you likely want to use Testing Library. 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.

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.

function useSomething(){
    throw new Error("something failed");
}
Enter fullscreen mode Exit fullscreen mode

The following examples in this article are written for Jest, but the same concept applies to other testing frameworks like Mocha or Cypress.

The old way before React 18 ❌

Before the release of React 18 renderHook was provided in a separated package called @testing-library/react-hooks. 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:

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

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 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, 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:

test('should throw an error', () => { 
    expect(() => {  
        renderHook(() => useSomething());  
    }).toThrow('something failed');  
});
Enter fullscreen mode Exit fullscreen mode

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.

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

test('should throw an error', () => { 
  try {
    renderHook(() => useSomething());
  }
  catche(error){
    expect(error).toBe('something failed')
  }
});
Enter fullscreen mode Exit fullscreen mode

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 for further information.

Top comments (4)

Collapse
 
zerico007 profile image
Bavin Edwards • Edited
renderHook(() => {
      try {
        useSomething();
      } catch (error) {
        expect(error.message).toEqual('some error occurred');
     }
})
Enter fullscreen mode Exit fullscreen mode

This prevents error from being shown in the console

Collapse
 
fairhandcream profile image
Fair Handcream • Edited

This test will pass if useSomething doesn't throw an error.
We can add an assertion to ensure is called at least once.

expect.assertions(1); // this assertion ensures 1 expect is called in this test

renderHook(() => {
  try {
    useSomething();
  } catch (error) {
    expect(error.message).toEqual('some error occurred');
  }
});
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jonmferreira profile image
Jonathan Maia Ferreira

Que maneiro, e isso nΓ£o tΓ‘ muito claro na doc do @testing-library/react, deveria tΓ‘ num tΓ³pico em grande destaque.
Obrigado pelo artigo fera !

Collapse
 
alexclaes profile image
Alex Claes

Thanks for the feedback. If you have an idea on how to improve the docs, you can go ahead an contribute to them ;-)
github.com/testing-library/testing...