Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing error thrown by a React component using testing-library and jest

Following Kent C Dodds' provider pattern explained in this blog post, I have a context provider component along with a hook to use that context.

The hook guards against the use of it outside of the provider,

export function useUser() {
  const { user } = useContext(UserContext) || {};
  const { switchUser } = useContext(SwitchUserContext) || {};
  if (!user || !switchUser) {
    throw new Error('Cannot use `useUser` outside of `UserProvider`');
  }
  return { user, switchUser };
}

To test the scenario, I create a TestComponent and use the useUser hook inside it.

function TestComponent() {
  const { user, switchUser } = useUser();
  return (
    <>
      <p>User: {user.name}</p>
      <button onClick={switchUser}>Switch user</button>
    </>
  );
}

I test it like this,

  test('should throw error when not wrapped inside `UserProvider`', () => {
    const err = console.error;
    console.error = jest.fn();
    let actualErrorMsg;
    try {
      render(<TestComponent />);
    } catch(e) {
      actualErrorMsg = e.message;
    }
    const expectedErrorMsg = 'Cannot use `useUser` outside of `UserProvider`';
    expect(actualErrorMsg).toEqual(expectedErrorMsg);

    console.error = err;
  });

I currently have to mock console.error and later set it to its original value at the end of the test. It works. But I'd like to make this more declarative and simpler. Is there a good pattern to achieve it? Something using .toThrow() perhaps?

I have a codesandbox for this, the above code can be found in UserContext.js and UserContext.test.js.

Note: Tests can be run in the codesandbox itself under the Tests tab.

like image 216
Bhargav Shah Avatar asked Feb 23 '21 07:02

Bhargav Shah


2 Answers

As you already mentioned there is expect().toThrow() :)

So in your case:

  test("should throw error when not wrapped inside `UserProvider`", () => {
    expect(() => render(<TestComponent />))
      .toThrow("Cannot use `useUser` outside of `UserProvider`");
  });

Regarding the console.error: Currently there is by design no way to turn off the default error logs. If you want to hide errors, you still need to mock console.error.

When you mock functions like console.error you want to restore them in a afterEach callback so that they are also restored if the test fails.

like image 69
Philipp Fritsche Avatar answered Sep 20 '22 03:09

Philipp Fritsche


You could do something like this

test('should throw error when not wrapped inside `UserProvider`', () => {
  component.useUser = jest.fn().mockRejectedValue(new Error('Cannot use `useUser` outside of `UserProvider`'));
  let actualErrorMsg;
  try {
    render(<TestComponent />);
  } catch(e) {
    actualErrorMsg = e.message;
  }
  const expectedErrorMsg = 'Cannot use `useUser` outside of `UserProvider`';
  expect(actualErrorMsg).toEqual(expectedErrorMsg);
});
like image 21
Yadab Avatar answered Sep 23 '22 03:09

Yadab