在拒绝承诺后测试回滚的最佳方法?



我经常遇到这样的情况,我想实现的行为是这样的:

  1. 用户执行操作
  2. 网站更新UI乐观
  3. 网站触发更新到服务器
  4. 网站等待服务器响应
  5. 如果更新失败,网站回滚UI更改

我经常发现自己在测试中添加多个超时,试图既断言乐观更新已完成,又在拒绝后回滚。像这样:

it('rolls back optimistic update', async () => {
jest.mocked(doUpdate).mockReturnValue(new Promise((resolve, reject) => {
setTimeout(reject, 1000);
});
render(<App />)
await screen.findByText('not done')
userEvent.click(screen.getByText('do it'))
await screen.findByText('Done!')
await screen.findByText('not done')
});

但这有一些相当大的缺点:

  1. 设置这样的测试是烦躁和困难的。
  2. 在测试中使用超时将导致测试比需要的速度慢。
  3. 测试环境或工具的任何重大更改都很有可能破坏这些测试。
  4. 测试变得更慢,更复杂,一旦我需要测试的东西,如确保后续的用户操作不会破坏之前的操作。
  5. 如果承诺在测试结束后解决,我经常以React抱怨更新没有被包装在act中结束。

我怎样才能以一种尽可能高效且对测试环境变化健壮的方式测试这种类型的行为?

我现在在这样的情况下使用这个辅助函数,我想控制事件和承诺的精确顺序:

export default function loadControlledPromise(mock: any) {
let resolve: (value?: unknown) => void = () => {
throw new Error('called resolve before definition');
};
let reject: (value?: unknown) => void = () => {
throw new Error('called reject before definition');
};
const response = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
jest.mocked(mock).mockReturnValue(response);
return { resolve, reject };
}
使用这个辅助函数,示例测试变成:
it('rolls back optimistic update', async () => {
const { reject } = loadControlledPromise(doUpdate);
render(<App />)
await screen.findByText('not done')
userEvent.click(screen.getByText('do it'))
await screen.findByText('Done!')
reject();
await screen.findByText('not done')
});

这确保:

  • 测试不会花费过多的等待时间。
  • 测试环境的变化不太可能破坏测试。
  • 可以测试更复杂的事件序列,而不会导致难以理解的测试代码。
  • 我可以在测试结束前强制承诺解决。即使我需要在承诺解决之前测试条件,也有助于避免React抱怨在act之外发生的更新。

相关内容

最新更新