【问题标题】:React/Jest/Enzyme - await not waiting long enoughReact/Jest/Enzyme - 等待时间不够长
【发布时间】:2019-10-27 01:45:06
【问题描述】:

我有一个等待多个承诺的函数

const function = async () => {
    await function1()
    await function2()
    await function3()
}

我想测试一下是否调用了function3:

it(('calls function3', async () => {
    jest.spyOn(api, 'function1').mockResolvedValue({})
    jest.spyOn(api, 'function2').mockResolvedValue({})
    spy = jest.spyOn(api, 'function3')
    await function()
    expect(spy).toBeCalledTimes(1)
})

这个测试失败了,但是当我多次调用 await 时:

it(('calls function3', async () => {
    jest.spyOn(api, 'function1').mockResolvedValue({})
    jest.spyOn(api, 'function2').mockResolvedValue({})
    spy = jest.spyOn(api, 'function3')
    await await await await await function()
    expect(spy).toBeCalledTimes(1)
})

测试将通过。为什么是这样? await function() 不应该在进入下一个期望行之前解决所有的承诺吗?

编辑:awaited 函数越深,即 function4,我需要的 await 语句越多,但不是 1 比 1。

【问题讨论】:

  • function 是javascript保留字,你确定你的函数声明没有报错吗?
  • @Dario 抱歉,这是一个示例,而不是实际代码

标签: javascript reactjs ecmascript-6 jestjs enzyme


【解决方案1】:

在进入下一个期望行之前,await function() 不应该解决所有的承诺吗?

是的,await 将等待返回的Promise 再继续。

这是一个简单的工作示例:

const function1 = jest.fn().mockResolvedValue();
const function2 = jest.fn().mockResolvedValue();
const function3 = jest.fn().mockResolvedValue();

const func = async () => {
  await function1();
  await function2();
  await function3();
}

it('calls function3', async () => {
  await func();
  expect(function3).toHaveBeenCalled();  // Success!
})

如果await 没有像预期的那样等待,那么Promise 链很可能在某个时候中断。

这是一个损坏的Promise 链示例:

const function1 = jest.fn().mockResolvedValue();
const function2 = jest.fn().mockResolvedValue();
const function3 = jest.fn().mockResolvedValue();

const func = async () => {
  await function1();
  await function2();
  await function3();
}

const func2 = async () => {
  func();  // <= breaks the Promise chain
}

it('calls function3', async () => {
  await func2();
  expect(function3).toHaveBeenCalled();  // <= FAILS
})

多次调用await 会将剩余的测试函数排在PromiseJobs 队列后面多次,这可以让待处理的Promise 回调有机会运行...

...所以上面的破测试如果改成这样就可以通过了:

it('calls function3', async () => {
  await await await await func2();  // <= multiple await calls
  expect(function3).toHaveBeenCalled();  // Success...only because of multiple await calls
})

...但真正的解决方案是找到并修复Promise 链中断的位置:

const func2 = async () => {
  await func();  // <= calling await on func fixes the Promise chain
}

it('calls function3', async () => {
  await func2();
  expect(function3).toHaveBeenCalled();  // Success!
})

【讨论】:

  • 承诺链在等待同步函数时中断?
  • 不,如果 await 没有在应该是链的一部分的 Promise 上调用,则 Promise 链会中断。在示例中,从 func 返回的 Promise 链接到从 function1function2function3 返回的 Promises...但 func2 func 上调用await,因此从func2 返回的Promise 不会链接到另一个Promises。修复func2 以在func 上调用await 修复了链并确保从func2 返回的Promise 一直链接到function3 返回的Promise。 @peter176
【解决方案2】:

promise 在微任务队列中排队的顺序是一个问题,我使用flush-promises 来解决同样的问题。

它使用节点setImmediate 向队列推送一个回调,该回调将在微任务队列为空时调用。

const flushPromises = require('flush-promises');

test('flushPromises', async () => {
  let a;
  let b;

  Promise.resolve().then(() => {
    a = 1;
  }).then(() => {
    b = 2;
  })

  await flushPromises();

  expect(a).toBe(1);
  expect(b).toBe(2);
});

【讨论】:

  • 有不需要导入其他库的解决方案吗?
  • 这个lib是9行代码:],你可以复制到你的项目中。
【解决方案3】:

现在有一个 proposal in Jest 有类似 runAllTimers 的东西,但用于承诺。

因此,如果您不想集成flush-promises,您可以只使用setTimeout(() =&gt; {...rest code...}, 0)。由于timeout 是宏任务,因此可以保证在运行之前解决所有待处理的微任务(如承诺)。

更多关于微任务和宏任务:https://abc.danch.me/microtasks-macrotasks-more-on-the-event-loop-881557d7af6f

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-04-06
    • 2021-10-11
    • 1970-01-01
    • 2023-03-26
    • 1970-01-01
    • 2019-03-14
    • 1970-01-01
    • 2022-11-03
    相关资源
    最近更新 更多