【问题标题】:How would I test this promise based code with jest?我将如何用 jest 测试这个基于 Promise 的代码?
【发布时间】:2019-03-11 12:26:08
【问题描述】:

我将如何测试这段代码?我想确保根据需要调用传递的承诺的错误和成功。我敢肯定这有点简单,但它让我发疯。非常感谢。

handleStatusChangeRequest (changeEntryStatus) {
  return changeEntryStatus().then(() => {
    this.handleStatusChangeSuccess()
  }).catch(err => {
    this.handleErrorDisplay(err)
  })
}

【问题讨论】:

  • 您对断言有什么想法?快照、模拟和验证方法调用,...?
  • 这只是完整的笑话测试套件。 . .不带摩卡咖啡或类似的东西。就在react-create-app (sp?) 工具链中。

标签: javascript promise jestjs


【解决方案1】:

如果您的代码使用 Promise,则有一种处理异步测试的好方法。只需从您的测试中返回 promiseJest 将等待该 promise 解决。
如果承诺被拒绝,测试将自动失败。

例如,假设changeData 不使用回调,而是返回一个promise,它应该解析为字符串“状态已成功修改”

请务必return promise - 如果您省略此返回语句,您的测试将在您的 changeData() -[async function] 完成之前完成.

这是一个方便且易于遵循的模式

test('if the data is changed', () => {
  return changeData().then((data) => {
    expect(data).toBe('status has been successfully modified');
  });
})

测试愉快:)

【讨论】:

  • 抱歉,没看到。您的第一个示例中的代码不是有效的 js?在handleStatusChangeRequest 通话后遗漏了一些东西。谢谢。
【解决方案2】:

这可以重构,但为了演示,我把重复的位留在里面。

example.spec.js 中,回调changeEntryStatus 被存根以返回一个promise。为了检查是否调用了其他实例方法(this.method),它们首先被模拟,然后在运行被测试的方法后在模拟上调用断言。在Jest docs 中了解更多信息。 (请参阅我对底部正在测试的单元的模拟方法的想法。)

Run the example on repl.it.

example.js:

class Example {
  handleStatusChangeRequest(changeEntryStatus) {
    return changeEntryStatus().then(() => {
      this.handleStatusChangeSuccess()
    }).catch(err => {
      this.handleErrorDisplay(err)
    })
  }

  handleStatusChangeSuccess() {
    console.log('stubbed handleStatusChangeSuccess')
  }

  handleErrorDisplay(error) {
    console.log('stubbed handleErrorDisplay:', error)
  }
}

module.exports = Example;

example.spec.js:

const Example = require('./entryStatus')
describe('handleStatusChangeRequest', () => {
  it('should run the changeEntryStatus callback', () => {
    const {handleStatusChangeRequest} = new Example()
    const stub = jest.fn().mockResolvedValue()

    handleStatusChangeRequest(stub)

    // must return because handleStatusChangeRequest is asynchronous
    return expect(stub).toHaveBeenCalled()
  });

  it('should call example.handleStatusChangeSuccess', async () => {
    const example = new Example()
    const stub = jest.fn().mockResolvedValue()
    example.handleStatusChangeSuccess = jest.fn()

    await example.handleStatusChangeRequest(stub)

    expect(example.handleStatusChangeSuccess).toHaveBeenCalled();
  })

  it('should call example.handleErrorDisplay', async () => {
    const example = new Example()
    const fakeError = { code: 'fake_error_code' }
    const stub = jest.fn().mockRejectedValue(fakeError)
    example.handleErrorDisplay = jest.fn()


    await example.handleStatusChangeRequest(stub)

    expect(example.handleErrorDisplay).toHaveBeenCalled()
    expect(example.handleErrorDisplay).toHaveBeenCalledWith(fakeError)
  });
});

Opinionated Disclaimer:被测单元的模拟方法是一种气味。考虑检查调用handleStatusChangeSuccesshandleErrorDisplay 的预期效果,而不是检查它们是否被调用。然后甚至不要公开这些方法,除非类的消费者需要访问。

【讨论】:

  • 嘿酷,我喜欢这个。我采用了类似的方法。嗯,但我确实在一系列与此承诺序列无关的不同测试中测试了其他功能。我认为这里的模拟很好,因为我只想确保序列在成功/错误场景中按预期运行。有意义吗?
【解决方案3】:

Opinionated Disclaimer: 被测单元的模拟方法是 闻。考虑检查调用的预期效果 handleStatusChangeSuccesshandleErrorDisplay 而不是检查 看看他们是否被召唤。然后甚至不要公开这些方法 公开,除非该类的消费者需要访问。

完全同意 webprojohn 的免责声明。模拟是一种气味,因为测试应该断言代码的行为,而不是它的实现。测试后者会使代码变脆。

离开我的肥皂盒... :) 我们正在寻找一种测试异步方法的方法。我不确定您的测试应该做出哪些断言来验证 handleStatusChangeSuccess()handleErrorDisplay(err) 内部的行为,因此下面的示例留下了这些断言所在的注释。以下使用Promise.resolve()Promise.reject() 来触发要测试的结果。我用过 async/await,Jest 有other async examples in their docs

const Example = require('./example')

describe('handleStatusChangeRequest', () => {
  it('should resolve successfully', async () => {
    const {handleStatusChangeRequest} = new Example();
    const resolvePromise = () => Promise.resolve();

    await handleStatusChangeRequest(resolvePromise);

    // resolution assertions here
  });

  it('should resolve errors', async () => {
    const {handleStatusChangeRequest} = new Example();
    const fakeError = new Error('eep');
    const rejectPromise = () => Promise.reject(fakeError);

    // if your method doesn't throw, we can remove this try/catch
    // block and the fail() polyfill
    try {
      await example.handleStatusChangeRequest(rejectPromise);

      // if we don't throw our test shouldn't get here, so we
      // polyfill a fail() method since Jest doesn't give us one.
      // See https://github.com/facebook/jest/issues/2129
      expect(true).toBe(false);
    }
    catch (e) {
      // rejection assertions here
    }
  });
});

【讨论】:

  • 嗯,很酷。 :) 我喜欢测试中的 try catch。我应该更多地尝试这种方法。
【解决方案4】:

我的答案是这样的:

**Success tests
const instance = el.find(EntryToolBar).instance()
const spy = jest.spyOn(instance, 'handleStatusChangeSuccess')

await instance.handleStatusChangeRequest(() => Promise.resolve('cool man'))

expect(spy).toHaveBeenCalledTimes(1)

**Error tests
const instance = el.find(EntryToolBar).instance()
const spy = jest.spyOn(instance, 'handleErrorDisplay')

await instance.handleStatusChangeRequest(() => Promise.reject(Error('shit')))
expect(spy).toHaveBeenCalledTimes(1)

如上所述,handleStatusChangeSuccesshandleError 方法在其他地方使用一些快照进行测试(它们只是设置状态并渲染出一些不同的 jsx)。我对此感觉很好。我正在使用间谍/模拟,但我正在其他地方测试实现功能。够了吗?

【讨论】:

  • 从问题看来,这两种方法似乎都是组件的内部结构。是否有任何理由应该在组件的公共接口中公开?如果没有,我会重构快照以成为handleStatusChangeRequest 测试的一部分,并将内部方法设为私有。测试你的公共接口的行为,“不要测试你的私有接口。”
  • 不,这是真的,它们都可以是私有方法。也许是静态的。为什么不测试私有方法?
  • 这是一种测试哲学:当我们破坏代码的意图时,测试应该警告我们,而不是如何它是如何做到的。如果由于我们重命名方法而导致测试失败(例如,这里将是 handleErrorDisplay -> handleError)但我们没有更改方法内部,则认为是严重失败或误报。希望对您有所帮助,如果您想进一步讨论,我们可以将其带到私人聊天中。
  • 嗯,我想我会认为这是一个非常好的失败。如果我更改事物的名称,我肯定希望在依赖于该名称的事物上引发错误。非常感谢您一直以来的帮助。哈哈,毫无疑问,你将来可能会收到我的电子邮件,骚扰你关于其他代码的事情。
  • 不,当您手动检查组件但测试失败时,该组件更有可能仍然工作。 IDE 并不总是能捕捉到诸如 jest.spyOn(component, 'handleErrorDisplay') 这样的字符串用法。但无论如何,祝你好运,测试愉快。
猜你喜欢
  • 1970-01-01
  • 2015-03-30
  • 2018-03-29
  • 2020-04-21
  • 2019-09-30
  • 2015-04-07
  • 2018-07-06
  • 1970-01-01
相关资源
最近更新 更多