测试工具

测试工具

本指南涵盖 Lucid 的测试工具。你将学会如何:

  • 为测试运行设置数据库 schema
  • 在测试之间重置数据
  • 将每个测试包装在拆卸时回滚的事务中
  • 直接从测试体中对表状态进行断言
  • 将上述任何操作限定到特定连接

概述

Lucid 通过两个入口暴露测试工具:

  • testUtils.db() 来自 @adonisjs/core/services/test_utils,返回一个 DatabaseTestUtils 实例,用于在整个测试运行中设置、清理和隔离数据库。
  • dbAssertions 插件来自 @adonisjs/lucid/plugins/japa/db,将数据库断言作为 db 访问器添加到 Japa 的 TestContext 上。

两者是独立的。你可以在测试钩子中使用 testUtils.db() 而不注册 Japa 插件,或者注册插件而不使用其他测试工具。

设置 schema

将两个钩子连接到你的测试设置中:在运行器级别使用 migrate() 来应用并稍后回滚每个迁移,在组级别使用 truncate() 来在单个测试之间清除数据。

tests/bootstrap.ts
import testUtils from '@adonisjs/core/services/test_utils'
export const runnerHooks: Required<Pick<Config, 'setup' | 'teardown'>> = {
setup: [
() => testUtils.db().migrate(),
],
teardown: [],
}

migrate() 在测试运行器启动时运行一次 migration:run 并返回一个清理函数,在运行器退出时运行 migration:reset。schema 为运行中的每个测试准备就绪,数据库在进程完成时重置为空。

在每个测试组内部,将 truncate() 注册为 group.each.setup 钩子,以在单个测试之间重置数据。

tests/functional/posts.spec.ts
import { test } from '@japa/runner'
import testUtils from '@adonisjs/core/services/test_utils'
test.group('Posts', (group) => {
group.each.setup(() => testUtils.db().truncate())
})

truncate() 返回一个清理函数,在测试完成后清空每个表。组中的每个测试都从运行器钩子定义的 schema 开始,加上组自己的设置(种子、fixture)插入的任何数据。

运行种子

seed() 通过 db:seed 命令运行配置的种子。将其与 truncate 钩子配对,以便在测试之间重置种子数据。

test.group('Subscriptions', (group) => {
group.each.setup(async () => {
const cleanup = await testUtils.db().truncate()
await testUtils.db().seed()
return cleanup
})
})

来自 truncate() 的清理仍然在每个测试后运行,因此种子数据仅在一个测试的持续时间内存在。

将每个测试包装在事务中

wrapInGlobalTransaction() 是组级别 truncate() 的替代方案。它在每个测试之前开始一个全局事务并在结束时回滚它,因此测试写入的内容实际上不会被持久化。这是最快的隔离机制,因为除了回滚之外不需要清理。

test.group('Users', (group) => {
group.each.setup(() => testUtils.db().wrapInGlobalTransaction())
})

Lucid 在测试内运行的每个查询都通过同一个事务运行,因此写入对同一测试中的后续查询可见,但在事务回滚时消失。

这种模式适用于:

  • 仅通过 Lucid 与数据库交互的测试
  • 不生成需要看到已提交状态的子进程或外部 worker 的测试

不适用于:

  • 模拟多个并发事务的测试,因为一次只能打开一个事务
  • 调用另一个进程的测试,因为另一个进程无法看到未提交事务内的写入

对于这些情况,请改用 truncate()

withGlobalTransaction()wrapInGlobalTransaction() 的已弃用别名。对新测试使用非弃用的名称。

数据库断言

dbAssertions Japa 插件将 db 属性添加到 Japa 的 TestContext,其中包含直接对表状态进行断言的方法。

注册插件

将插件添加到你的 Japa 配置中。

tests/bootstrap.ts
import app from '@adonisjs/core/services/app'
import { dbAssertions } from '@adonisjs/lucid/plugins/japa/db'
export const plugins: Config['plugins'] = [
dbAssertions(app),
]

注册后,从测试上下文中解构 db 以访问断言方法。

test('creates a user', async ({ client, db }) => {
await client.post('/users').json({ email: 'virk@adonisjs.com' })
await db.assertHas('users', { email: 'virk@adonisjs.com' })
})

可用的断言

assertHas(table, data, count?)

当表至少有一行匹配 data 中的每个列值对时通过。当提供 count 时,仅在恰好存在那么多匹配行时通过。

// 至少一行
await db.assertHas('users', { email: 'virk@adonisjs.com' })
// 恰好两行匹配
await db.assertHas('posts', { author_id: 1, is_published: true }, 2)

assertMissing(table, data)

当没有行匹配给定的列值对时通过。

await db.assertMissing('users', { email: 'deleted@example.com' })

assertCount(table, expectedCount)

当表恰好有 expectedCount 行总数时通过。

await db.assertCount('users', 3)

assertEmpty(table)

assertCount(table, 0) 的简写。

await db.assertEmpty('sessions')

assertModelExists(model)

当数据库中存在的行具有模型的主键时通过。接受模型实例并读取其 $primaryKeyValue

const user = await User.findOrFail(1)
await db.assertModelExists(user)

如果模型没有设置主键值则抛出异常。

assertModelMissing(model)

当模型的主键不存在行时通过。在删除后用于确认行已消失。

const user = await User.findOrFail(1)
await user.delete()
await db.assertModelMissing(user)

限定到特定连接

当你的应用使用多个连接时,每个工具都接受可选的连接名称。使用它对非默认数据库运行相同的设置、清理或断言。

// 特定连接上的设置钩子
group.each.setup(() => testUtils.db('analytics').truncate())
// 特定连接上的事务隔离
group.each.setup(() => testUtils.db('analytics').wrapInGlobalTransaction())
// 特定连接上的断言
await db.connection('analytics').assertHas('events', { kind: 'signup' })

断言对象上的 db.connection(name) 方法返回限定到命名连接的新断言助手;它不修改默认助手。