如何等待测试库中不发生某些事情?

Tam*_*lyn 7 jestjs react-testing-library testing-library

我想断言一个条件,我知道该条件不会立即为真,但在异步操作之后可能为真,此时测试应该失败。

假设我正在测试这个计数器组件:

function Counter() {
  const [value, setValue] = useState(1);
  function decrement() {
    if (value >= 0) { // <- off by one bug
      someAsyncAction().then(() => setValue(value - 1));
    }
  }
  return (
    <>
      Value is {value}
      <button onClick={decrement}>Decrement</button>
    </>
  );
}

Run Code Online (Sandbox Code Playgroud)

我可以编写此测试来检查该值不应低于零:

const button = screen.getByRole("button", { name: "Decrement" });
expect(screen.getByText("Value is 1")).toBeInTheDocument();

userEvent.click(button);
expect(await screen.findByText("Value is 0")).toBeInTheDocument();

userEvent.click(button);
// !!! wrong !!!
expect(screen.getByText("Value is 0")).toBeInTheDocument();
expect(screen.queryByText("Value is -1")).not.toBeInTheDocument();
// !!! wrong !!!
Run Code Online (Sandbox Code Playgroud)

但是最后两个断言总是会通过,即使组件有一个错误,这意味着它将异步更新以显示“值是-1”。

处理这种情况的推荐方法是什么?

Tam*_*lyn 14

我想出的最好的办法是:

await expect(
  screen.findByText("Value is -1", {}, { timeout: 100 })
).rejects.toThrow();
Run Code Online (Sandbox Code Playgroud)

它尝试查找文本,等待超时(从默认的 1 秒减少到 100 毫秒以加快测试速度),然后拒绝 捕获的文本expect。如果文本存在,则findByText调用将解析,因此将expect拒绝并且测试将失败(不要忘记awaitexpect

该模式可以使用以下方法扩展到其他断言waitFor

await expect(
  waitFor(
    () => { expect(screen.getByRole('input')).toHaveValue(-1); },
    { timeout: 100 }
  )
).rejects.toThrow();
Run Code Online (Sandbox Code Playgroud)

虽然这可行,但有点复杂,尤其是waitFor形式。我觉得这仍然可以改进,所以如果您有任何建议,请提出。


Ale*_*ied 5

采用Tamlyn 出色的自我回答中提出的概念,我将其推断为一个名为的实用程序,该实用程序具有与测试库verifyNeverOccurs相同的签名,但只有在您发送给它的回调断言通过时才会失败:waitFor

import { waitFor, waitForOptions } from '@testing-library/react';

/**
 * Inverse of RTL's `waitFor`; used to verify that a thing does *not* occur. 
 * Useful for situations in which verifying that some effect did occur would
 * require using `await waitFor()` and you need to test that the effect does
 * not occur. Like `waitFor`, it must be `await`ed.
 * @param {function} negativeAssertionFn - a callback function that expects a thing you do _not_ expect will occur
 * @param {Object} options - options object with same shape as `waitFor`'s options argument (ultimately just passed through to `waitFor`)
 * @return {void}
 */

const verifyNeverOccurs = async (negativeAssertionFn: () => unknown, options?: waitForOptions) => {
  await expect(
    waitFor(negativeAssertionFn, options),
  ).rejects.toThrow();
};

export default verifyNeverOccurs;
Run Code Online (Sandbox Code Playgroud)

(这是在 TypeScript 中,但如果你想在 vanilla JS 中使用,你可以随时删除类型)。

用法示例:

// fails if `element` ever contains the text "oh no"
verifyNeverOccurs(() => expect(element).toHaveTextContent('oh no'));
Run Code Online (Sandbox Code Playgroud)