---
# System prepended metadata

title: React Common Testing Patterns
tags: [farobot, frontend, testing]

---


# React Common Testing Patterns

## Stateful component

```jsx
test('my component updates state on button click', () => {
  // Render the component
  const { getByText } = render(<MyComponent />);

  // Find the button element
  const button = getByText('Click me');

  // Use the act function to wrap the code that interacts with the component
  act(() => {
    // Click the button
    fireEvent.click(button);
  });

  // Assert that the component's state has been updated as expected
  expect(getByText('Button has been clicked')).toBeInTheDocument();
});

```

## Container component

```jsx
test('my container passes data to its child component', () => {
  // Render the container with some data
  const data = [{ id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }];
  const { getByText } = render(<MyContainer data={data} />);

  // Assert that the child component receives the correct data
  data.forEach(item => {
    expect(getByText(item.name)).toBeInTheDocument();
  });
});
```

## Higher-order component

```jsx!
import React from 'react';
import {
  render, fireEvent, screen,
} from '@testing-library/react';

// This is the higher-order component that we want to test
function withLoadingIndicator(WrappedComponent: React.ComponentType<any>) {
  return function WithLoadingIndicator(props: any) {
    const { isLoading, ...otherProps } = props;
    if (isLoading) {
      return <div>Loading...</div>;
    }
    // eslint-disable-next-line react/jsx-props-no-spreading
    return <WrappedComponent {...otherProps} />;
  };
}

// This is the wrapped component that the higher-order component will be used with
function MyButton(props: any) {
  const { name, onClick } = props;
  return (
  // eslint-disable-next-line react/button-has-type
    <button onClick={onClick}>
      {name}
    </button>
  );
}

describe('withName', () => {
  let MyLoadingButton: React.ComponentType<any>;

  beforeEach(() => {
    MyLoadingButton = withLoadingIndicator(MyButton);
  });

  it('returns a valid React component', () => {
    // First, we'll make sure that the higher-order component
    // returns a valid React component when it's called
    expect(MyLoadingButton).toBeInstanceOf(Function);
  });

  it('renders the wrapped component with the correct props', () => {
    // Next, we'll render the returned component and make sure
    // that it correctly renders the wrapped component with the correct props
    const { getByText } = render(<MyLoadingButton name="Click me!" />);
    const button = getByText('Click me!');
    expect(button).toBeDefined();
  });

  it('handles events correctly', () => {
    // Then, we'll test that the returned component correctly handles events
    const handleClick = jest.fn();
    const { getByText } = render(<MyLoadingButton name="Click me!" onClick={handleClick} />);
    const button = getByText('Click me!');
    fireEvent.click(button);
    expect(handleClick).toHaveBeenCalled();
  });

  it('passes isLoading=true, test the isLoading effect', () => {
    // Finally, we'll test that the returned component correctly
    // passes additional props down to the wrapped component
    const handleClick = jest.fn();
    const { getByText, queryByText } = render(<MyLoadingButton isLoading name="Click me!" onClick={handleClick} />);
    const div = getByText('Loading...');
    expect(div).toBeDefined();

    const button = queryByText('Click me!');
    expect(button).toBeNull();
  });

  it('passes isLoading=false, test the isLoading effect', () => {
    // Finally, we'll test that the returned component correctly
    // passes additional props down to the wrapped component
    const handleClick = jest.fn();
    const { getByText, queryByText } = render(<MyLoadingButton isLoading={false} name="Click me!" onClick={handleClick} />);
    const div = queryByText('Loading...');
    expect(div).toBeNull();

    const button = getByText('Click me!');
    expect(button).toBeDefined();
  });
});

```

## Custom hook
### Simple case: `useState`
- borrow the codes from https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning#2-when-testing-custom-hooks
```jsx=
import React from 'react';
import {
  act, renderHook
} from '@testing-library/react';

function useCount() {
  const [count, setCount] = React.useState(0);
  const increment = () => setCount((c) => c + 1);
  const decrement = () => setCount((c) => c - 1);
  return { count, increment, decrement };
}

describe('useCount', () => {
  it('should increment and decrement updates the count', () => {
    const { result } = renderHook(() => useCount());
    expect(result.current.count).toBe(0);

    act(() => result.current.increment());
    expect(result.current.count).toBe(1);

    act(() => result.current.decrement());
    expect(result.current.count).toBe(0);
  });
});
```

### Complex case: `useEffect` and `axios` mocking
- key insights come from https://cultivate.software/useeffect/
    - Q: How do I test a useEffect hook?
        - Indirectly!
        - Always test from the user’s perspective.
- other good resources
    - https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning
    - https://testing-library.com/docs/queries/about

```jsx=
import React from 'react';
import axios from 'axios'; // 因為在 test case 中直接 mock axios.get，所以需要在 test file 中 import
import {
  render,
} from '@testing-library/react';

const useInitData = (url:string) => {
  const [data, setData] = React.useState(null);
  React.useEffect(() => {
    axios.get(url)
      .then((res) => setData(res.data));
  }, [url]);

  return [data];
};

// NOTE: 做一個 dummy component，只為了測試用
const DummyComponent = ({ url }: { url:string }) => {
  const [data] = useInitData(url);
  return (
    <div>
      hello:
      {JSON.stringify(data, null, '')}
    </div>
  );
};

// jest.mock('axios');
// NOTE: 找到的文章都說要使用 `jest.mock` 。但發現這個 PoC 不需要。有關如何用最簡潔且正確的方式 mocking axios，需另外 survey。
describe('useInitData', () => {
  it('should init the data', async () => {
    const url = 'http://empty.domain';
    axios.get = jest.fn().mockResolvedValue({ data: { foo: 'hahapoint' } });

    const { findByText, queryByText } = render(<DummyComponent url={url} />);
    const div = await findByText('hello', { exact: false });
    expect(div).toBeDefined();

    const div2 = queryByText('world', { exact: false });
    expect(div2).toBeNull();
  });
});

```