【问题标题】:Jest spyOn call count after mock implementation throwing an error模拟实现抛出错误后的 Jest spyOn 调用计数
【发布时间】:2021-05-27 16:52:07
【问题描述】:

我有一个程序按此顺序发出三个post 请求

  1. http://myservice/login
  2. http://myservice/upload
  3. http://myservice/logout

代码看起来像这样

async main() {
    try {
        await this.login()
        await this.upload()
    } catch (e) {
        throw e
    } finally {
        await this.logout()
    }
  }

每个方法在失败时都会抛出自己的错误。

我正在使用 Jest 来监视底层请求库(超级代理)。对于一项特定的测试,我想测试如果上传函数抛出错误,则正在发出注销post 请求。

我通过抛出异常来模拟 post 请求。

const superagentStub = {
    post: () => superagentStub
}

const postSpy = jest.spyOn(superagent, 'post')
  .mockImplementationOnce(() => superagentStub)
  .mockImplementationOnce(() => { throw new Error() })
  .mockImplementationOnce(() => superagentStub)

const instance = new ExampleProgram();

expect(async () => await instance.main()).rejects.toThrow(); // This is fine
expect(postSpy).toHaveBeenNthCalledWith(3, 'http://myservice/logout')

如果我不模拟第三个实现,测试将失败,因为logout() 将抛出自己的error,因为第三个post 请求将作为实时调用失败。

本案例中的间谍报告仅对底层库的 post 方法进行了 1 次调用。

  1. http://myservice/login

我觉得这很奇怪,因为我期待 3 次呼叫间谍

  1. http://myservice/login
  2. http://myservice/upload -> 但它会引发错误
  3. http://myservice/logout

【问题讨论】:

    标签: javascript unit-testing jestjs


    【解决方案1】:

    请记住如何使用expect(...).rejects.toThrow()。不过,这有点棘手:https://jestjs.io/docs/expect#rejects

    顺便说一句:使用 JavaScript 编码时激活 ESLint 总是很好的。然后,以下规则可能会警告您您的错误:https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/valid-expect.md(“必须等待或返回异步断言。”)

    解决方案

    您在测试代码倒数第二行的开头缺少await。用以下内容替换该行应该有望解决您的问题:

    await expect(() => instance.main()).rejects.toThrow();
    

    相同
    await expect(async () => await instance.main()).rejects.toThrow();
    

    您在变体中声明 // This is fine,但实际上并非如此。你有一个“误报”,那里。即使你否定它,Jest 很可能也会接受你测试的倒数第二行,即如果你用 .rejects.not.toThrow() 替换 .rejects.toThrow()

    如果您在同一个测试套件中有多个测试,Jest 可能会改为声明稍后的某个测试失败 - 即使它实际上是导致问题的第一个测试。

    详情

    在给定行的开头没有新的await,会发生以下情况:

    1. expect(...).rejects.toThrow() 发起 instance.main() - 但不等待创建的 Promise 解决或拒绝。
    2. instance.main() 的开头同步运行到第一个await,即this.login() 被调用。
    3. 主要是因为superagent.post() 的模型是同步的,this.login() 将立即返回。 顺便说一句:总是用异步模型替换异步函数可能是个好主意,例如使用.mockResolvedValueOnce()
    4. Promise 仍在等待中; JavaScript 现在运行您的测试代码的最后一行,并且 Jest 声明您的模型只使用过一次(到现在为止)。
    5. 测试因该错误而中止。
    6. 之后对instance.main() 的调用很可能会继续,从而导致instance.main() 内部出现预期错误、Promise 被拒绝和您的模型的三种用法 - 但所有这些在测试已经失败之后。

    【讨论】:

    • 是的,就是这样!我们的 linter 启用了valid-expect,但没有启用alwaysAwait,因此感谢您指出问题以及其他问题
    猜你喜欢
    • 1970-01-01
    • 2020-08-10
    • 1970-01-01
    • 2020-09-27
    • 2017-11-29
    • 2021-09-22
    • 1970-01-01
    • 2023-03-04
    • 2020-10-05
    相关资源
    最近更新 更多