用 Jest 异步测试 Koa

用 Jest 异步测试 Koa

原文:https://medium.com/hackernoon/async-testing-koa-with-jest-1b6e84521b71

Koa is a species of tree on Hawaii (not pictured). Photo by Ozark Drones.

利用鲜为人知的特性简化 API 和中间件测试

这是一份在柏林会议上所做报告的文字记录。

什么是 Koa,什么是 Jest

让我们简单描述一下我们将要使用的库。

Koa(koajs.com)是一个 JavaScript web 服务器框架。它是由一个更著名的 Express 背后的人开发的,作为一个轻量级和富有表现力的“精神继承者”。

它也是基于中间件的。但是,它没有附带任何功能。是的,没有路由器,没有主体解析器,也没有代理。利用 ES2017 异步/等待功能带来用户友好的功能和端到端流量控制。(你不熟悉 async/await ?阅读穆斯塔法·加法尔的这篇有用的介绍。我们会经常使用它们。)这意味着我们可以对回调地狱说‘拜拜’了,我们从 Express 了解到。我们将使用第二个版本;在 Koa v1 发电机提供了流量控制。

Jest 是一个来自脸书的现代单元测试框架。它给主流 JavaScript 测试带来了几个概念:零配置、一流的模仿和快照。

如果说 Koa 是 Express 的(精神)继承者,Jest 可谓是 Jasmine 和 Expect(后来捐赠给 Jest )的(精神)继承者。

五月发布的包含了一个更好的异步/承诺支持,我认为它还没有得到应有的关注。我希望它是清楚的,那是我们以后将使用的。

就个人而言,我对 Jest 更有经验,我每天都用它来测试基于 React 的应用。另一方面,Koa 是我的爱好,我只把它用在私人项目上。

测试中间件

Koa 中间件通常具有以下形式:

const greetings = async (ctx, next) => {
  ctx.body = 'Hello.' await next() ctx.body += ' Remember to subscribe.'
}const app = new Koa()
app.use(greetings)
app.listen(3000)

它接收包含请求和响应信息的上下文ctx。改变这个对象是中间件通信的唯一方式。没有返回值;如果有,它将被忽略。因此,测试中间件意味着观察上下文的变化。

第二个参数是回调,它挂起当前的中间件,并将控制权传递给下一个中间件。这个回调要么被等待,要么根本不被调用——启动中间件订单执行。当然,可能不会调用多次。

Read more details in the well-written guide, from where I borrowed this animation

简单测试

上面的greeting中间件的一个简单测试如下所示:

test('greetings works', async () => {
  const ctx = {} await greetings(ctx, () => {}) expect(ctx.body).toBe(
    'Hello. Remember to subscribe.'
  )
})

首先,是的,你可以开玩笑地使用 async 。框架将等待所有异步操作完成。

next回调是一个空函数——这是最低要求。它只是将流立即返回给我们的函数。

上下文对象是一个模拟对象。我们可以提供其他数据,如请求的网址或标题。

当整个中间件完成时,我们在上下文对象上运行断言。

如您所见,我们无法区分变化是何时发生的——“之前”还是“之后”await next(),我们只知道结果。然而,对于许多中间件来说,这已经足够了。尤其是那些只在下一个中间件之前或之后运行小程序的应用程序。

如果我们需要更多呢?

前后测试

如果在传递到下一个中间件之前或之后运行,读取文件、记录时间和生成 ETag 是必要的。

此类测试可能如下所示:

test('greetings works before-and-after', async () => {
  const ctx = {} const next = jest.fn(() => {
    expect(ctx.body).toBe('Hello.')  // (1)
    ctx.body += ' I am content.'
  }) await greetings(ctx, next) expect(next).toHaveBeenCalledTimes(1) expect(ctx.body).toBe(             // (2)
    'Hello. I am content. Remember to subscribe.'
  )
})

哇,长高了一点。但是不要害怕,这很容易。

最重要的变化是next内部的回调。它不再是 noop 了。这是用(1)标记的地方,我们在这里测试ctx如何“以前”改变。在这里,我们也可以为在(2)断言的“之后”部分做准备。

请注意,我们将next包装在jest.fn中,这样我们可以检查它的运行以及其中的断言是否通过。否则,可以通过不调用await next()来跳过它们。如果您在单独的测试中测试它,则可以移除它。或者,我们可以在顶部添加expect.assertions(2)——并保持更新。

一个小小的警告:我见过有人试图通过调用中间件而不等待来将测试分成两部分。这是错误和危险的,因为“之后”部分也可能被调用。此外,如果在“以前”中有异步操作,它将不起作用

完整的中间件测试

下面是一个使用快照测试的更新示例。额外的好处是,它将捕捉额外的变化,否则你可能会错过。

它还展示了如何在 Koa 提供的工具上测试函数调用,在我们的例子中是response.set。它使用了一个简短的版本来测试调用次数和每个调用中的参数,顺序敏感。

test('greetings works complete', async () => {
  const ctx = {
    response: { set: jest.fn() }
    /* ADD OTHER MOCKS */
  } const next = jest.fn(() => {
    expect(ctx).toMatchSnapshot()
  }) await expect(greetings(ctx, next))
    .resolves.toBeUndefined() expect(next).toHaveBeenCalledTimes(1) expect(ctx).toMatchSnapshot()
  expect(ctx.response.set.mock.calls).toMatchSnapshot()
})

你可以看到,最后一个增强是在 Jest 20 中添加的.resolves matcher。它做两件事:检查中间件不返回任何东西,并在出现问题时提供更好的消息。比较:

之前:

Read error

之后:

Expected received Promise to resolve, instead it rejected to value
 [Error: Read error]

当我们预计会出现错误时,这种差异会更加明显。更多内容,请看我的演示示例:玩笑式异步测试

接下来呢?

测试小单元。这是不是意味着我们结束了?不。我喜欢这个 GIF:

Still love this one. Unit testers be like: “Looks like it’s working” — expect.anything()[expect.any(constructor)](http://facebook.github.io/jest/docs/en/expect.html#expectanyconstructor)[expect.stringContaining(string)](http://facebook.github.io/jest/docs/en/expect.html#expectstringcontainingstring)[expect.stringMatching(regexp)](http://facebook.github.io/jest/docs/en/expect.html#expectstringmatchingregexp),还有一些其他的

Jest 将尝试匹配整个对象。所以当我们添加字段nationality时,它会失败。要忽略其他属性,使用expect.objectContaining,就像我们处理整个主体一样(记住它也包括字段data)。

快照

还有一个替代方案:快照。可能已经注意到,它们与 TDD 相反。人们无法合理地遵循 TDD 并使用快照。当我们在观察模式下一点一点地逐步构建 API 时,或者当我们为已经工作的服务器添加测试时,它们显示了它们的力量。

从快照开始再简单不过了:

expect(response.body).toMatchSnapshot()

并且保存了快照:

// test/__snapshots__/root.spec.js.snapexports[`root route with snapshots 1`] = `
Object {
  "data": "Sending some JSON",
  "person": Object {
    "age": 42,
    "lastname": "Vaněk",
    "name": "Ferdinand",
    "role": "Brewery worker",
  },
}
`;

我要重复一遍:这再简单不过了。

然而,它确保我们返回我们需要的东西,而不是额外的东西——这同样重要。

不仅是 Koa

当然,我们展示的一切也适用于其他框架。例如,API 测试对于 Express 来说是一样的——对于重构来说是多么的方便!

所有代码在 GitHub 上都有:robinpokorny/jest-example-KOA

如果您有任何意见或建议,请联系我。我喜欢解谜,所以我会看你发给我的任何关于 Jest 的问题。

幻灯片:

Slides from the presentation in Berlin

相关:

如果你喜欢这个帖子,请别忘了给个👏下面的。每一个鼓掌通知对我来说都是一种激励。

如果你想了解更多,我最近在 YouTube 上开了一个关于 JavaScript 的频道。我每周都会发布新视频,所以可以考虑订阅。从一开始就在那里,帮助我变得更好。

[## YouTube 上的罗宾·波科尔尼

JavaScript 是我的激情所在:我喜欢写 JavaScript,我喜欢读 JavaScript,我喜欢说 JavaScript。

www.youtube.com/c/robinpokorny](https://www.youtube.com/c/robinpokorny?sub_confirmation=1)


本站为非盈利网站,作品由网友提供上传,如无意中有侵犯您的版权,请联系删除