samuraime / how-to-test-react-hooks

How to test react hooks?

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to test react hooks? (How to test components that use hooks?)

TLDR

Rules of Hooks: Only Call Hooks from React Functions

If you need to test a custom Hook, you can do so by creating a component in your test, and using your Hook from it. Then you can test the component you wrote. (copied).

Even though hooks are just JavaScript functions, they will work only inside React components.

You cannot just invoke them and write tests against what a hook returns. You have to wrap them inside a React component and test the values that it returns.

In some cases, hooks may not even return a value, so we have to test components that use hooks.

Test returns of hooks OR Test components that use hooks?

As react-hooks-testing-library said

  • When to

    • You're writing a library with one or more custom hooks that are not directly tied a component
    • You have a complex hook that is difficult to test through component interactions
  • When not to

    • Your hook is defined along side a component and is only used there
    • Your hook is easy to test by just testing the components using it

If you are one of the following, please read on.

  • you don't know how to test a component
  • you want to reduce the boilerplate
  • you have a complex hook that is difficult to test through component interactions

Test react built-in hooks

Testing Libraries

Most of the libraries here are for testing components, not for testing the return value of hooks. But We can write a helper function that exposes the result of hooks from inside the component, I will show that by enzyme.

I will use following useCounter to show how to test.

import { useState, useCallback } from 'react';

function useCounter() {
  const [count, setCount] = useState(0);
  const increment = useCallback(() => setCount((x) => x + 1), []);

  return { count, increment };
}

export default useCounter;

the easiest and most verbose solution, don't need to intro any libraries. official example

import React from 'react';
import ReactDOM from 'react-dom';
import { act } from 'react-dom/test-utils';
import useCounter from '../useCounter';

function Counter() {
  const { count, increment } = useCounter();

  return (
    <button onClick={increment}>{count}</button>
  );
}

let container;

beforeEach(() => {
  container = document.createElement('div');
  document.body.appendChild(container);
});

afterEach(() => {
  document.body.removeChild(container);
  container = null;
});

it('should increment counter', () => {
  // Test first render and effect
  act(() => {
    ReactDOM.render(<Counter />, container);
  });
  const button = container.querySelector('button');
  expect(button.textContent).toBe('0');

  // Test second render and effect
  act(() => {
    button.dispatchEvent(new MouseEvent('click', { bubbles: true }));
  });
  expect(button.textContent).toBe('1');
});

This solution is similar to react-dom/test-utils, just in order to reduce the boilerplate.

import React from 'react';
import { render, fireEvent, waitForElement } from '@testing-library/react';
import useCounter from '../useCounter';

function Counter() {
  const { count, increment } = useCounter();

  return (
    <button data-testid="button" onClick={increment}>{count}</button>
  );
}

it('should increment counter', () => {
  const { getByTestId } = render(<Counter />);

  const button = getByTestId('button');
  expect(button.textContent).toBe('0');

  fireEvent.click(button)
  expect(button.textContent).toBe('1');
});
  • Test component

This solution is similar to react-dom/test-utils as well.

import React from 'react';
import { shallow } from 'enzyme';
import useCounter from '../useCounter';

function Counter() {
  const { count, increment } = useCounter();

  return (
    <button onClick={increment}>{count}</button>
  );
}

it('should increment counter', () => {
  const wrapper = shallow(<Counter />);
  expect(wrapper.find('button').text()).toBe('0');

  wrapper.find('button').simulate('click');
  expect(wrapper.find('button').text()).toBe('1');
});
  • Test returns of hooks

This demo can only cover simple scenarios, see the next demo for a more robust solution.

import React from 'react';
import { shallow } from 'enzyme';
import useCounter from '../useCounter';

it('should increment counter', () => {
  let count;
  let increment;

  function Counter() {
    ({ count, increment } = useCounter());

    return count;
  }

  shallow(<Counter />);
  expect(count).toBe(0);
  increment();
  expect(count).toBe(1);

  // const wrapper = shallow(<Counter />);
  // expect(wrapper.text()).toBe('0');
  // increment();
  // expect(wrapper.text()).toBe('0');
});

Obviously, we can abstract out the render logic

import React from 'react';
import { shallow } from 'enzyme';
import useCounter from '../useCounter';

function renderHook(hook) {
  let result = { current: null };

  function Counter() {
    const currentResult = hook();

    result.current = currentResult;

    return null;
  }

  shallow(<Counter />);

  return result;
};

it('should increment counter', () => {
  const result = renderHook(useCounter);
  
  expect(result.current.count).toBe(0);

  result.current.increment();

  expect(result.current.count).toBe(1);
});

When to use this library

  • You're writing a library with one or more custom hooks that are not directly tied a component
  • You have a complex hook that is difficult to test through component interactions

When not to use this library

  • Your hook is defined along side a component and is only used there
  • Your hook is easy to test by just testing the components using it
import { renderHook, act } from '@testing-library/react-hooks';
import useCounter from '../useCounter';

it('should increment counter', () => {
  const { result } = renderHook(() => useCounter());

  expect(result.current.count).toBe(0);

  act(() => {
    result.current.increment();
  });

  expect(result.current.count).toBe(1);
});

Run the demo

yarn test

Reference

License

MIT

About

How to test react hooks?

License:MIT License


Languages

Language:JavaScript 100.0%