Skip to content

模拟和伪造

在测试应用程序时,您可能希望模拟或伪造特定依赖项以防止实际实现运行。例如,您希望在运行测试时避免向客户发送电子邮件,也不调用第三方服务(如支付网关)。

AdonisJS 提供了一些不同的 API 和建议,您可以使用它们来伪造、模拟或存根依赖项。

使用 fakes API

Fakes 是专门为测试创建的具有工作实现的对象。例如,AdonisJS 的邮件模块有一个伪造实现,您可以在测试期间使用它来收集内存中的发送邮件并为它们编写断言。

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

依赖注入和 fakes

如果您在应用程序中使用依赖注入或使用容器创建类实例,您可以使用 container.swap 方法为类提供伪造实现。

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

ts
import UserService from '#services/user_service'
import { inject } from '@adonisjs/core'

export default class UsersController {
  @inject()
  index(service: UserService) {}
}

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

ts
import UserService from '#services/user_service'
import app from '@adonisjs/core/services/app'

test('获取所有用户', async () => {
  // highlight-start
  class FakeService extends UserService {
    all() {
      return [{ id: 1, username: 'virk' }]
    }
  }
  
  /**
   * 用 `FakeService` 的实例替换 `UserService`
   */  
  app.container.swap(UserService, () => {
    return new FakeService()
  })
  
  /**
   * 测试逻辑在这里
   */
  // highlight-end
})

测试完成后,您必须使用 container.restore 方法恢复伪造。

ts
app.container.restore(UserService)

// 恢复 UserService 和 PostService
app.container.restoreAll([UserService, PostService])

// 恢复全部
app.container.restoreAll()

使用 Sinon.js 进行模拟和存根

Sinon 是 Node.js 生态系统中成熟、经过时间考验的模拟库。如果您经常使用模拟和存根,我们建议使用 Sinon,因为它与 TypeScript 配合得很好。

模拟网络请求

如果您的应用程序向第三方服务发出传出 HTTP 请求,您可以在测试期间使用 nock 来模拟网络请求。

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

冻结时间

编写测试时,您可以使用 timekeeper 包来冻结或穿越时间。timekeeper 包通过模拟 Date 类来工作。

在以下示例中,我们将 timekeeper 的 API 封装在 Japa 测试资源中。

ts
import { getActiveTest } from '@japa/runner'
import timekeeper from 'timekeeper'

export function timeTravel(secondsToTravel: number) {
  const test = getActiveTest()
  if (!test) {
    throw new Error('不能在 Japa 测试之外使用 "timeTravel"')
  }

  timekeeper.reset()

  const date = new Date()
  date.setSeconds(date.getSeconds() + secondsToTravel)
  timekeeper.travel(date)

  test.cleanup(() => {
    timekeeper.reset()
  })
}

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

ts
import { timeTravel } from '#test_helpers'

test('2 小时后令牌过期', async ({ assert }) => {
  /**
   * 穿越到 3 小时后的未来
   */
  timeTravel(60 * 60 * 3)
})