模拟与伪造

模拟(Mocks)与伪造(Fakes)

在测试应用时,你可能希望模拟或伪造特定的依赖,以防止真实的实现被执行。例如,你希望在运行测试时不给客户发送邮件,也不调用支付网关等第三方服务。

AdonisJS 提供了几种不同的 API 和建议,你可以借此伪造、模拟或桩化(stub)某个依赖。

使用 fakes API

Fakes 是专门为测试而创建、具有可工作实现的对象。例如,AdonisJS 的 mailer 模块有一个 fake 实现,你可以在测试期间使用它,将发出的邮件收集到内存中并为其编写断言。

我们为以下容器服务提供了 fake 实现。fakes API 与模块文档一同记录。

依赖注入与 fakes

如果你在应用中使用依赖注入,或使用容器来创建类实例,你可以使用 container.swap 方法为某个类提供一个 fake 实现。

在以下示例中,我们将 UserService 注入到 UsersController 中。

import UserService from '#services/user_service'
import { inject } from '@adonisjs/core'
export default class UsersController {
@inject()
index(service: UserService) {}
}

在测试期间,我们可以为 UserService 类提供一个替代/伪造的实现,如下所示。

import UserService from '#services/user_service'
import app from '@adonisjs/core/services/app'
test('get all users', async () => {
class FakeService extends UserService {
all() {
return [{ id: 1, username: 'virk' }]
}
}
/**
* Swap `UserService` with an instance of
* `FakeService`
*/
app.container.swap(UserService, () => {
return new FakeService()
})
/**
* Test logic goes here
*/
})

测试完成后,你必须使用 container.restore 方法恢复 fake。

app.container.restore(UserService)
// Restore UserService and PostService
app.container.restoreAll([UserService, PostService])
// Restore all
app.container.restoreAll()

使用 Sinon.js 进行 mock 和 stub

Sinon 是 Node.js 生态系统中一个成熟、经过时间检验的 mocking 库。如果你经常使用 mock 和 stub,我们建议使用 Sinon,因为它与 TypeScript 配合得很好。

模拟网络请求

如果你的应用会向第三方服务发起对外的 HTTP 请求,你可以在测试期间使用 nock 来模拟网络请求。

由于 nock 会拦截来自 Node.js HTTP 模块的所有对外请求,因此它几乎适用于所有第三方库,例如 gotaxios 等。

冻结时间

在编写测试时,你可以使用 timekeeper 包来冻结或穿越时间。timekeeper 包的工作原理是模拟 Date 类。

在以下示例中,我们将 timekeeper 的 API 封装在一个 Japa 测试资源(Test resource)中。

import { getActiveTest } from '@japa/runner'
import timekeeper from 'timekeeper'
export function timeTravel(secondsToTravel: number) {
const test = getActiveTest()
if (!test) {
throw new Error('Cannot use "timeTravel" outside of a Japa test')
}
timekeeper.reset()
const date = new Date()
date.setSeconds(date.getSeconds() + secondsToTravel)
timekeeper.travel(date)
test.cleanup(() => {
timekeeper.reset()
})
}

你可以在测试中如下使用 timeTravel 方法。

import { timeTravel } from '#test_helpers'
test('expire token after 2 hours', async ({ assert }) => {
/**
* Travel 3 hours into the future
*/
timeTravel(60 * 60 * 3)
})