测试简介
AdonisJS 具有开箱即用的测试支持,无需安装任何第三方包。只需运行node ace test
,奇迹就会发生。
注:AdonisJS 使用 Japa(一个自制的测试框架)来编写和执行测试。 因此,我们强烈建议你阅读 Japa 文档一次。
每次全新安装的 AdonisJS 都会附带一个在tests/functional/hello-world.spec.ts
文件中编写的示例功能测试。让我们打开这个文件并了解如何用 AdonisJS 编写测试。
import { test } from '@japa/runner'
test('display welcome page', async ({ client }) => {
const response = await client.get('/')
response.assertStatus(200)
response.assertTextIncludes('<h1 class="title"> It Works! </h1>')
})
- 使用
@japa/runner
包导出的test
函数注册测试。 test
函数接受标题作为第一个参数,实现回调作为第二个参数。- 实现回调接收测试上下文。测试上下文包含可用于获得更好测试体验的其他属性。
让我们通过执行以下命令来运行测试。
node ace test
# [ info ] 运行测试...
# tests/functional/hello-world.spec.ts
# ✔ 显示欢迎页面 (24ms)
# PASSED
# total : 1
# passed : 1
# duration : 28ms
现在让我们重新运行测试命令,但这次使用--watch
标志。观察者将观察文件系统更改并在每次文件更改后执行测试。
node ace test --watch
测试套件
AdonisJS 将测试组织成多个套件。每个套件的测试都位于其子目录中。例如:
- 功能测试存储在
tests/functional/
目录中。 - 单元测试存储在
tests/unit/
目录中。
套件在.adonisrc.json
文件中注册,你可以根据需要删除/添加套件。套件结合了文件的唯一名称和 glob 模式。
注:你也可以使用
make:suite
命令创建一个新的测试套件并将其注册到.adonisrc.json
文件中。
// title: .adonisrc.json
{
"tests": {
"suites": [
{
"name": "functional",
"files": "tests/functional/**/*.spec(.ts|.js)"
}
]
}
}
你还可以为每个测试套件注册生命周期挂钩。这些钩子使用configureSuite
方法在tests/bootstrap.ts
文件中注册。
在下面的例子中,AdonisJS 为功能测试套件注册了一个设置钩子来启动 HTTP 服务。
export const configureSuite: Config['configureSuite'] = (suite) => {
if (suite.name === 'functional') {
suite.setup(() => TestUtils.httpServer().start())
}
}
配置测试运行器
AdonisJS 在项目根目录中的test.ts
文件中配置测试运行程序。该文件首先启动 AdonisJS 应用程序,然后使用 Japa 运行测试。
在大多数情况下,你永远不会触及test.ts
文件。相反,我们建议你使用tests/bootstrap.ts
文件来进一步配置测试运行程序或在测试之前/之后运行自定义逻辑。
引导文件导出以下属性,然后将其提供给 Japa。
// title: tests/bootstrap.ts
export const plugins: Config['plugins'] = []
export const reporters: Config['reporters'] = []
export const runnerHooks: Required<Pick<Config, 'setup' | 'teardown'>> = {
setup: [],
teardown: [],
}
export const configureSuite: Config['configureSuite'] = (suite) => {
}
插件
plugins
属性接受一系列 Japa 插件。默认情况下,我们注册以下插件。
assert
- 断言模块进行断言。runFailedTests
- 仅运行失败测试(如果有)的插件。apiClient
- 用于测试 HTTP 端点的 API 客户端。
reporters
reporters
属性接受一组 Japa 记者。我们注册 spec-reporter
以在终端显示测试进度。
runnerHooks
你可以使用runnerHooks
属性在测试之前或之后运行操作(跨所有套件)。
-setup
钩子在所有测试之前执行。
-teardown
钩子在所有测试后执行。
配置套件
configureSuite
方法使用 Japa suite 类的实例执行。你可以使用套件实例对其进行配置。
环境变量
在测试期间,AdonisJS 会自动将NODE_ENV
的值设置为test
。
我们还加载.env.test
文件并将该文件中定义的值与现有环境变量合并。默认情况下定义了以下覆盖。
NODE_ENV=test
ASSETS_DRIVER=fake
SESSION_DRIVER=memory
ASSETS_DRIVER
属性将用于提供 捆绑资产 的驱动程序切换为假实现。这样做允许你在不使用 Webpack 编译前端资源的情况下运行测试。SESSION_DRIVER
切换到在内存中持久化会话数据并在测试期间访问它。使用任何其他驱动程序都会破坏测试。
创建测试
你可以使用 node ace make:test
命令创建测试。该命令接受套件名称作为第一个参数,然后是测试文件名。
node ace make:test functional list_users
# CREATE: tests/functional/list_users.spec.ts
你可以按如下方式创建嵌套文件结构。
node ace make:test functional users/list
# CREATE: tests/functional/users/list.spec.ts
运行测试
你可以通过执行 node ace test
命令来运行测试。此外,你可以通过传递套件名称来运行特定套件的测试。
# 运行所有测试
node ace test
# 只执行功能测试
node ace test functional
# 单元和功能测试按顺序执行
node ace test unit functional
# 仅在“单元”和“功能”套件中使用“订单”或“上传”标签进行测试
node ace test --tags="orders,upload" unit functional
test
命令接受以下标志。
--watch
:在监视模式下运行测试。如果测试文件被更改,观察者将只运行修改后的文件中的测试。否则,将执行所有测试。--tags
:运行具有一个或多个上述标签的测试。--ignore-tags
:--tags
标志的逆向。只运行没有所有提到的标签的测试。--files
:从提到的文件中挑选并运行测试。--timeout
:定义所有测试的全局超时。--force-exit
:如果测试过程没有正常结束,则强制退出。--tests
:按标题运行特定测试。
数据库管理
本节介绍数据库迁移、运行播种器以及使用全局事务在测试之间获得干净的数据库状态。
注意:确保你已安装
@adonisjs/lucid
以使以下示例正常工作。
迁移数据库
你可以在运行所有测试之前迁移数据库并在测试之后回滚它。这可以通过在 tests/bootstrap.ts
文件中注册 TestUtils.db().migrate()
挂钩来完成。
// 文件名: tests/bootstrap.ts
export const runnerHooks: Required<Pick<Config, 'setup' | 'teardown'>> = {
setup: [
() => TestUtils.ace().loadCommands(),
() => TestUtils.db().migrate()
],
teardown: [],
}
种子数据库
你还可以通过调用 TestUtils.db().seed()
方法来运行数据库播种器。
setup: [
() => TestUtils.ace().loadCommands(),
() => TestUtils.db().migrate()
() => TestUtils.db().seed()
],
全局事务
我们建议你使用 数据库全局事务 在测试之间获得干净的数据库状态。
在下面的示例中,我们在所有测试之前启动一个全局事务并在测试之后回滚它。
提示:
group.each.setup
方法在组内的每个测试之前运行。
import Database from '@ioc:Adonis/Lucid/Database'
test.group('Group name', (group) => {
group.each.setup(async () => {
await Database.beginGlobalTransaction()
return () => Database.rollbackGlobalTransaction()
})
})
如果你使用多个数据库连接,那么你可以为每个连接定义一个挂钩。例如:
group.each.setup(async () => {
await Database.beginGlobalTransaction('pg')
return () => Database.rollbackGlobalTransaction('pg')
})
group.each.setup(async () => {
await Database.beginGlobalTransaction('mysql')
return () => Database.rollbackGlobalTransaction('mysql')
})
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。