Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
3.4k views
in Technique[技术] by (71.8m points)

reactjs - Mocked useHistory is not called in async event handler

Summary

I'm writing test code for my react app, but somehow, it always fails.

My app code is very simple, there is only one button, and if it's clicked, a function handleSubmit is fired.

What the handler does are

  • Fetching data from backend(This is async function)
  • Move to /complete page.

What I did

  • I mocked the function fetching data from API in test code
  • I mocked the useHistory in test code

Note

I realized that if the line that is fetching data from API is commented out, the test will pass.

Code

  • My main app code
import { useFetchDataFromAPI } from '@/usecase/useFetchDataFromAPI';

  :

  const { fetchDataFromAPI } = useFetchDataFromAPI();

  :

  const handleSubmit = async () => {
    // If the line below is not commented out, test will fail
    // const { id } = await fetchDataFromAPI();
    history.push(`/complete`);
  };

  return (
    <>
      <button onClick={handleSubmit}>Button</button>
    </>
  • My test code

:

jest.mock('@/usecase/useFetchDataFromAPI', () => ({
  useFetchDataFromAPI: () => {
    return { fetchDataFromAPI: jest.fn((): number => {
        return 1;
    })}
  }
}));

const mockHistoryPush = jest.fn();
jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom') as any,
  useHistory: () => ({
    push: mockHistoryPush,
  }),
}));

:

const renderApplicationWithRouterHistory = () => {
  const history = createMemoryHistory();
  const wrapper = render(
    <Router history={history}>
      <Application />
    </Router>
  );
  return { ...wrapper, history };
};

:

describe('Test onClick handler', async () => {
  test('Submit', () => {
    const { getByText, getByRole } = renderApplication();

    const elementSubmit = getByText('Button');
    expect(elementSubmit).toBeInTheDocument();
    fireEvent.click(elementSubmit);
    expect(mockHistoryPush).toHaveBeenCalled();
  });
});

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Your event handler is called on button click, but because it is asynchronous, its result is not evaluated until after your test runs. In this particular case, you don't need the async behavior, so just use:

const handleSubmit = () => {
  history.push(`/complete`)
}

testing-library provides a method waitFor for this if your handler did need to await something:

await waitFor(() => expect(mockHistoryPush).toHaveBeenCalled())

Though another simple way is to simply await a promise in your test so that the expectation is delayed by a tick:

fireEvent.click(elementSubmit);
await Promise.resolve();
expect(mockHistoryPush).toHaveBeenCalled();

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...