测试工具
本指南涵盖 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() 来在单个测试之间清除数据。
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 钩子,以在单个测试之间重置数据。
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 配置中。
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) 方法返回限定到命名连接的新断言助手;它不修改默认助手。