🧑‍💻测试 React Hooks 的完整指南 🗓 + Demo 🍿

钩子阵营16.8介绍了2018年后期他们是函数勾成一个功能组件,并允许我们使用状态和组件的功能,如componentDidUpdatecomponentDidMount等。这在以前是不可能的。

此外,钩子允许我们跨不同组件重用组件和状态逻辑。这在以前很难做到。因此,钩子已经改变了游戏规则。

在本文中,我们将探索如何测试 React Hooks。我们将选择一个足够复杂的钩子并进行测试。

我们希望您是一名已经熟悉 React Hooks的狂热 React 开发人员。如果你想复习知识,你应该查看我们的教程,这里是官方文档的链接。

我们将用于测试的钩子

对于本文,我们将使用我在上一篇文章Stale-while-revalidate Data Fetching with React Hooks 中编写的钩子。钩子被称为useStaleRefresh。如果您还没有阅读这篇文章,请不要担心,因为我将在此处回顾该部分。

这是我们将要测试的钩子:

import { useState, useEffect } from "react";
const CACHE = {};

export default function useStaleRefresh(url, defaultValue = []) {
  const [data, setData] = useState(defaultValue);
  const [isLoading, setLoading] = useState(true);

  useEffect(() => {
    // cacheID is how a cache is identified against a unique request
    const cacheID = url;
    // look in cache and set response if present
    if (CACHE[cacheID] !== undefined) {
      setData(CACHE[cacheID]);
      setLoading(false);
    } else {
      // else make sure loading set to true
      setLoading(true);
      setData(defaultValue);
    }
    // fetch new data
    fetch(url)
      .then((res) => res.json())
      .then((newData) => {
        CACHE[cacheID] = newData;
        setData(newData);
        setLoading(false);
      });
  }, [url, defaultValue]);

  return [data, isLoading];
}

如您所见,useStaleRefresh是一个钩子,它有助于从 URL 获取数据,同时返回数据的缓存版本(如果存在)。它使用一个简单的内存存储来保存缓存。

如果还isLoading没有可用的数据或缓存,它还会返回一个值为 true的值。客户端可以使用它来显示加载指示器。isLoading当缓存或新鲜响应可用时,该值设置为 false。

在这一点上,我建议你花一些时间阅读上面的钩子,以全面了解它的作用。

在本文中,我们将看到如何测试这个钩子,首先不使用测试库(只有 React Test Utilities 和 Jest),然后使用react-hooks-testing-library。

不使用测试库,即只使用一个测试运行器Jest,背后的动机是为了演示测试钩子是如何工作的。有了这些知识,您将能够调试在使用提供测试抽象的库时可能出现的任何问题。

定义测试用例

在我们开始测试这个钩子之前,让我们想出一个我们想要测试的计划。既然我们知道钩子应该做什么,这是我测试它的八步计划:

  1. 当使用 URL 挂载钩子时url1isLoadingistrue和 data is defaultValue
  2. 在异步获取请求之后,钩子用数据更新data1并且isLoadingfalse
  3. 当 URL 更改为 时url2isLoading再次变为 true 并且数据为defaultValue
  4. 在异步获取请求之后,钩子被更新为新数据data2
  5. 然后,我们将 URL 改回url1. 由于数据data1被缓存,数据立即被接收。isLoading是假的。
  6. 在异步获取请求之后,当收到新的响应时,数据将更新为data3
  7. 然后,我们将 URL 改回url2. 由于数据data2被缓存,数据立即被接收。isLoading是假的。
  8. 在异步获取请求之后,当收到新的响应时,数据将更新为data4

上面提到的测试流程清楚地定义了钩子将如何工作的轨迹。因此,如果我们能够确保此测试有效,我们就很好。

在没有库的情况下测试 Hook

在本节中,我们将看到如何在不使用任何库的情况下测试钩子。这将使我们深入了解如何测试 React Hooks。

为了开始这个测试,首先,我们想模拟fetch. 这样我们就可以控制 API 返回的内容。这是嘲笑的fetch.

function fetchMock(url, suffix = "") {
  return new Promise((resolve) =>
    setTimeout(() => {
      resolve({
        json: () =>
          Promise.resolve({
            data: url + suffix,
          }),
      });
    }, 200 + Math.random() * 300)
  );
}

此修改fetch假定响应类型始终为 JSON,并且默认情况下返回参数url作为data值。它还为响应添加了 200 毫秒到 500 毫秒之间的随机延迟。

如果我们想改变响应,我们只需将第二个参数设置suffix为非空字符串值。

说到这里,你可能会问,为什么要延迟?为什么我们不立即返回响应?这是因为我们想尽可能地复制现实世界。如果我们立即返回它,我们将无法正确测试钩子。当然,我们可以将延迟减少到 50-100 毫秒以加快测试速度,但在本文中我们不必担心。

准备好 fetch mock 后,我们可以将其设置为fetch函数。我们使用beforeAllandafterAll这样做是因为这个函数是无状态的,所以我们不需要在单独测试后重置它。

// runs before any tests start running
beforeAll(() => {
  jest.spyOn(global, "fetch").mockImplementation(fetchMock);
});

// runs after all tests have finished
afterAll(() => {
  global.fetch.mockClear();
});

然后,我们需要在组件中安装钩子。为什么?因为钩子本身就是函数。在组件,它们可应对只有当useStateuseEffect等等。

因此,我们需要创建一个TestComponent帮助我们挂载钩子的工具。

// defaultValue is a global variable to avoid changing the object pointer on re-render
// we can also deep compare `defaultValue` inside the hook's useEffect
const defaultValue = { data: "" };

function TestComponent({ url }) {
  const [data, isLoading] = useStaleRefresh(url, defaultValue);
  if (isLoading) {
    return <div>loading</div>;
  }
  return <div>{data.data}</div>;
}

这是一个简单的组件,它要么呈现数据,要么在数据正在加载(正在获取)时呈现“正在加载”文本提示。

一旦我们有了测试组件,我们就需要将它挂载到 DOM 上。我们使用beforeEachafterEach为每个测试挂载和卸载我们的组件,因为我们想在每次测试之前从一个新的 DOM 开始。

let container = null;

beforeEach(() => {
  // set up a DOM element as a render target
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  // cleanup on exiting
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

请注意,container它必须是一个全局变量,因为我们希望能够访问它以进行测试断言。

有了这个集合,让我们在渲染 URL 的地方做我们的第一个测试url1,因为获取 URL 需要一些时间(参见 参考资料fetchMock),它最初应该渲染“加载”文本。

it("useStaleRefresh hook runs correctly", () => {
  act(() => {
    render(<TestComponent url="url1" />, container);
  });
  expect(container.textContent).toBe("loading");
})

使用 运行测试yarn test,它按预期工作。这是GitHub 上完整代码

现在,让我们测试此文loading本何时更改为获取的响应数据url1

我们怎么做?如果您查看fetchMock,您会看到我们等待了 200-500 毫秒。如果我们sleep在等待 500 毫秒的测试中放入一个会怎样?它将涵盖所有可能的等待时间。让我们试试看。

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

it("useStaleRefresh hook runs correctly", async () => {
  act(() => {
    render(<TestComponent url="url1" />, container);
  });
  expect(container.textContent).toBe("loading");

  await sleep(500);
  expect(container.textContent).toBe("url1");
});

测试通过了,但我们也看到了一个错误(代码)。

 PASS  src/useStaleRefresh.test.js
  ✓ useStaleRefresh hook runs correctly (519ms)

  console.error node_modules/react-dom/cjs/react-dom.development.js:88
    Warning: An update to TestComponent inside a test was not wrapped in act(...).

这是因为useStaleRefreshhook 中的状态更新发生在act()之外。为了确保及时处理 DOM 更新,React 建议您act()在每次重新渲染或 UI 更新可能发生时使用。所以,我们需要结束我们的睡眠,act因为这是状态更新发生的时间。这样做后,错误就会消失。

import { act } from "react-dom/test-utils";
// ...
await act(() => sleep(500));

现在,再次运行它(GitHub 上的代码)。正如预期的那样,它通过而没有错误。

我们来测试下一种情况,我们先将 URL 更改为url2,然后检查loading屏幕,然后等待 fetch 响应,最后检查url2文本。由于我们现在知道如何正确等待异步更改,这应该很容易。

act(() => {
  render(<TestComponent url="url2" />, container);
});
expect(container.textContent).toContain("loading");

await act(() => sleep(500));
expect(container.textContent).toBe("url2");

运行这个测试,它也通过了。现在,我们还可以测试响应数据发生变化和缓存发挥作用的情况。

您会注意到suffix我们的fetchMock函数中有一个额外的参数。这是用于更改响应数据。所以我们更新我们的 fetch mock 以使用suffix.

global.fetch.mockImplementation((url) => fetchMock(url, "__"));

现在,我们可以测试url1再次设置 URL 的情况。它首先加载url1然后url1__. 我们可以对 做同样的事情url2,应该不会有什么意外。

it("useStaleRefresh hook runs correctly", async () => {
  // ...
  // new response
  global.fetch.mockImplementation((url) => fetchMock(url, "__"));

  // set url to url1 again
  act(() => {
    render(<TestComponent url="url1" />, container);
  });
  expect(container.textContent).toBe("url1");
  await act(() => sleep(500));
  expect(container.textContent).toBe("url1__");

  // set url to url2 again
  act(() => {
    render(<TestComponent url="url2" />, container);
  });
  expect(container.textContent).toBe("url2");
  await act(() => sleep(500));
  expect(container.textContent).toBe("url2__");
});

整个测试让我们确信钩子确实按预期工作(代码)。欢呼!现在,让我们快速浏览一下使用辅助方法的优化。

温馨提示 : 非特殊注明,否则均为©李联华的博客网原创文章,本站文章未经授权禁止任何形式转载;IP地址:3.14.250.187,归属地:俄亥俄州Dublin ,欢迎您的访问!
文章链接:https://www.lilianhua.com/%f0%9f%8d%bf%f0%9f%a7%91%f0%9f%92%bb-complete-guide-to-test-react-hooks-%f0%9f%97%93-demo-%f0%9f%8d%bf.html
Popup Image

通知

本站原则上是免费提供技术支持,但是服务器维护和运营成本高,可以实行自由赞助:赞助

Loading...