使用 Typescript 和测试库在 React 中进行 Axios Mocking

2025-06-09

使用 Typescript 和测试库在 React 中进行 Axios Mocking

我昨天遇到了一个问题,所以我想写一篇关于我的发现和解决方案的文章。

最后,它真的很简单(我越是卡在某个问题上,解决方案就越容易……🤷‍♂️)。我的 googlefu 完全失败了,因为我找到的每个解决方案要么过时,要么不完整,或者更重要的是:没有考虑到 Typescript。

这里使用的重要包(github链接在最后):

嗯。从哪儿开始呢?我们先从基本的useFetch 钩子开始,因为我们在这里使用 axios 来获取数据。

useFetch axios 钩子

export interface IUseFetch {
  response: any;
  loading: boolean;
  error: boolean;
}

export const useFetch = (run: boolean, url: string) => {
  const [response, setResponse] = useState({});
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(false);

  useEffect(() => {
    let mounted = true;
    const abortController = new AbortController();
    const signal = abortController.signal;
    if (run && mounted) {
      const fetchData = async () => {
        try {
          setLoading(true);
          const response = await axios.get(url);
          if (response.status === 200 && !signal.aborted) {
            setResponse(response.data);
          }
        } catch (err) {
          if (!signal.aborted) {
            setResponse(err);
            setError(true);
          }
        } finally {
          if (!signal.aborted) {
            setLoading(false);
          }
        }
      };
      fetchData();
    }

    return () => {
      mounted = false;
      abortController.abort();
    };
  }, [run, url]);

  return { response, loading, error };

}
Enter fullscreen mode Exit fullscreen mode

相当标准的 useFetch hook。run变量是触发 fetch 运行的触发器。

应用程序

接下来,我们来看看基本的 React 组件。这个组件只是一个输入框,它执行搜索操作,并显示一个 div,其中包含一些来自我们useFetch上面 hook 的搜索结果。


export interface ILocation {
  location: string;
  country: string;
}

export default function App() {
  const [searchString, setSearchString] = useState(""); 
  const [isPanelOpen, setIsPanelOpen] = useState(false); // show/hide results
  const [doSearch, setDoSearch] = useState(false); // controls fetch run

  // useFetch hook above.
  const { response, loading } = useFetch(doSearch, "test.json");

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setSearchString(e.target.value);
  };

  // If the searchString length > 0, then do the following
  useEffect(() => {
    setDoSearch(searchString.length > 0);
    setIsPanelOpen(searchString.length > 0);
  }, [searchString.length]);

  const renderSearchResults = () =>
    !loading &&
    !error &&
    response &&
    response.length > 0 && (
      <ul aria-label="search-results">
        {response.map((loc: ILocation, i: number) => (
          <li key={i}>
            {loc.location}, {loc.country}
          </li>
        ))}
      </ul>
    );

  return (
    <div className="App">
      <label htmlFor="search">Search:</label>
      <input
        type="text"
        aria-label="search-input" // label used by our tests
        id="search"
        name="search"
        autoComplete="off"
        value={searchString}
        onChange={handleChange}
      />

      {isPanelOpen && (
        <div aria-label="search-panel">{renderSearchResults()}</div>
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

够简单吗?太棒了。

现在进行测试。

测试

在我们开始之前,看看上面的代码,我们有三个 aria 标签可以用来断言。

  • search-input:我们的输入框
  • search-panel:搜索结果容器 div。根据响应,它可能显示为空(此处不做介绍)。
  • search-results:保存来自 useFetch hook 的实际 json 响应

首先,让我们准备测试文件。

创建一个名为的文件App.test.tsx并进行设置:

import '@testing-library/jest-dom';
import '@testing-library/jest-dom/extend-expect';
import React from 'react';
import {
  cleanup,
  render,
  fireEvent,
  wait,
} from '@testing-library/react';

import axios from 'axios';
import App from './App';

jest.mock('axios');

Enter fullscreen mode Exit fullscreen mode

注意事项:

  • @testing-library/jest-dom/extend-expect:允许我们对 jest-dom 进行一些有用的扩展,例如.toBeInTheDocument()
  • 我们正常导入 axios 不需要有趣的名字。
  • 我们做一个标准jest.mock('axios')这让我们的测试知道,每当他们看到一个 axios 导入时,就用一个模拟函数替换它。

你来这里的目的:模拟

在编写测试之前,我们先进行模拟。我们将模拟 axios,这也是我遇到困难的部分。其实很简单。

import { AxiosResponse } from 'axios';

// load our test.json file. This can be copied to the local 
// folder. Can be a short version of your actual data set.
const testJson = require('../../test.json');

// Our mocked response
const axiosResponse: AxiosResponse = {
  data: testJson,
  status: 200,
  statusText: 'OK',
  config: {},
  headers: {},
};

// axios mocked
export default {
  // Typescript requires a 'default'
  default: {
    get: jest.fn().mockImplementation(() => Promise.resolve(axiosResponse)),
  },
  get: jest.fn(() => Promise.resolve(axiosResponse)),
};

Enter fullscreen mode Exit fullscreen mode

这里发生的事情是,我们创建了一个模拟的 AxiosResponse,它包含所有必需品,例如response.status我们在 useFetch 钩子中使用的,然后是最重要的部分:response.data

然后我们有了实际的 axios mock。每当我们的应用看到 axios import 时,它就会使用其中的内容。我们get在本例中使用它,所以我添加了一个getmock。这里需要注意的是,我们有一个,default并且它是 Typescript 使用的。更多信息请点击此处

测试

接下来,我们编写测试。在这个测试中,我们将遵循Kent C. Dodds 在他的博客中提出的建议。因此,我们只进行一次端到端 (E2E) 测试。这将涵盖用户在输入框中输入内容并查看搜索结果的整个过程。


test("type text into input, and  display search results", async () => {
  // our test searchString
  const searchString = "syd";

  // Render App
  const { getByLabelText, queryByLabelText, debug } = render(<App />);

  // find the input
  const input = getByLabelText("search-input");

  // search panel should not be rendered at this point
  expect(queryByLabelText("search-panel")).not.toBeInTheDocument();

  // this fire the onChange event and set the value to 'syd'
  fireEvent.change(input, { target: { value: searchString } });

  // useFetch should be called to get data
  expect(axios.get).toHaveBeenCalled();

  // assert our input value to be searchString
  expect(input.value).toBe(searchString);

  // search panel is loaded in the document
  expect(queryByLabelText("search-panel")).toBeInTheDocument();

  // wait for search results to be rendered
  await wait(() => {
    expect(queryByLabelText("search-results")).toBeInTheDocument();
  });
});

Enter fullscreen mode Exit fullscreen mode

我们使用async是因为需要将await搜索结果呈现出来。

就是这样。在 TypeScript 中模拟 Axios 的关键在于返回 AxiosResponse 的模拟文件。然后,我们可以在测试中使用 assert expect(axios.get).toHaveBeenCalled()

这是 github repo 的链接,因为 Codesandbox 不支持 jest.mock。

如果您尝试一下,您可以通过注释整个 axios.ts 文件和 App.test.tsx 文件中的 jest.mock('axios') 来了解它是如何工作的。

希望这对某人有帮助。

鏂囩珷鏉ユ簮锛�https://dev.to/mnsr/axios-mocking-in-react-using-typescript-4da0
PREV
Kubernetes 入门指南 - 一步步
NEXT
像我五岁一样解释容器