0

我有一个简单形式的组件。它有一个input,当用户输入某些值时,它会使用该值进行 API 调用,以获取一个选项数组,然后用这些选项填充一个select.

我能够毫无问题地测试对输入的更改 - 通过模拟事件,我能够执行第一个挂钩。

但我的问题是我有两个useEffect相互链接的钩子。由于第一个钩子的内部被嘲笑,我不知道如何让它触发第二个钩子并呈现新的结果。

这是我正在使用的两个钩子的简化版本:

Component.tsx

React.useEffect(() => {
      const request = new XMLHttpRequest();
      request.open('GET', `${api}?arg=${inputValue}`, true);
      request.onload = (e: ProgressEvent) => {
        const res = e.target as XMLHttpRequest;
        if (res.status >= 200 && res.status < 400) {
          const data = JSON.parse(res.response);
          setList(data.list);
          }
        }
      };
      request.send();
  }, [inputValue]);

...

  React.useEffect(() => {
    select.current.innerHTML = '<option value="" disabled selected>Select...</option>';
    if (list.length > 1) {
      list.forEach((item) => {
        select.current.innerHTML += `<option value="${item.name}">${item.name}</option>`;
      });
    }
  }, [list]);

因此,当 inputValue 更改时,组件必须检索一个新列表。当列表更改时,<select>更新中的选项。

目前,我正在以类似于此处描述的方式模拟 XMLHttpRequest:https ://stackoverflow.com/a/43893305/

如何以触发内部更改的方式编写模拟此请求,以便执行第二个挂钩?还是我以错误的方式解决这个问题?

4

2 回答 2

0

我可以把它留给后代,以防有人像我一样困惑,但最好的解决方案是重构。

对我来说最简单的事情就是用 fetch 调用替换 XHR。这使我可以将响应保存到变量,并在以后处理它(将其保存到状态)。然后,在我的测试中,我使用了一个 fetch 的模拟实现来返回我想要的信息,并且我不需要在回调中设置 setState。

Component.tsx

const fetchOptions = async (input: string) => {
  try {
    const response = await fetch(`${api}?input=${input}`);
    const parsed: { list: IOption[] } = await response.json();
    setList(parsed.list)
  } catch (e) {
    setHasError(e)
  } finally {
    isRequesting = false;
  }

React.useEffect(() => {
  if (isRequesting) {
    return;
  }
  isRequesting = true;

  fetchOptions(value);
}, [inputValue]);
Component.test.js
...
const mockData = {
  list: [ { name: 'TEST' } ]
}

global.fetch = jest.fn().mockImplementation(() => {
  return new Promise((resolve) => {
    resolve({
      json: () => mockData,
    });
  });
});

我把我确切的模拟实现放在这里,因为我也很难弄清楚所需的特定语法。许多指南(包括官方文档)提供了一个不适用于我的应用程序的模拟实现。

根据 Drew Reeses 的建议,我还重构了我的另一个钩子以避免直接 DOM 操作,而是使用响应式表达式作为 select 的子项:

<select>
  <option value="" disabled selected>Select...</option>
  { list?.map(item => <option value={item.name} key={item.id}>{item.name}</option> }
</select>
于 2021-09-17T18:27:38.070 回答
0

在使用钩子测试组件时,使用@testing-library/react通常更容易(取决于钩子)。如果您习惯enzyme.

这是一篇不错的文章,展示了它的用途:https ://kentcdodds.com/blog/how-to-test-custom-react-hooks 。

您的用例的通用示例是

import { render } from '@testing-library/react';

// == within test case ==

  const { rerender, result } = render(<Component {...props} />);
  
  // assert something
  
  props.val = 'newVal';
  rerender(<Component {...props} />); // updates current component with new props

  // assert changes

您将用于result.current验证 DOM 结果是否以您期望的方式更改。

于 2021-09-17T20:39:48.077 回答