控制台测试

命令行测试

命令行测试指的是测试属于你应用或软件包代码库的自定义 Ace 命令。

在本指南中,我们将学习如何为命令编写测试、模拟日志记录器输出,以及拦截 CLI 提示。

基础示例

让我们先创建一个名为 greet 的新命令。

node ace make:command greet
import { BaseCommand } from '@adonisjs/core/ace'
import { CommandOptions } from '@adonisjs/core/types/ace'
export default class Greet extends BaseCommand {
static commandName = 'greet'
static description = 'Greet a username by name'
static options: CommandOptions = {}
async run() {
this.logger.info('Hello world from "Greet"')
}
}

让我们在 tests/unit 目录中创建一个 单元 测试。如果单元测试套件尚未定义,可以随时定义它

node ace make:test commands/greet --suite=unit
# DONE: create tests/unit/commands/greet.spec.ts

让我们打开新创建的文件并编写以下测试。我们将使用 ace 服务创建一个 Greet 命令的实例,并断言它成功退出。

import { test } from '@japa/runner'
import Greet from '#commands/greet'
import ace from '@adonisjs/core/services/ace'
test.group('Commands greet', () => {
test('should greet the user and finish with exit code 0', async () => {
/**
* Create an instance of the Greet command class
*/
const command = await ace.create(Greet, [])
/**
* Execute command
*/
await command.exec()
/**
* Assert command exited with status code 0
*/
command.assertSucceeded()
})
})

让我们使用以下 ace 命令运行该测试。

node ace test --files=commands/greet

测试日志记录器输出

Greet 命令目前会将日志消息写入终端。为了捕获这条消息并为其编写断言,我们必须将 ace 的 UI 库切换到 raw 模式。

raw 模式下,ace 不会向终端写入任何日志。相反,它会将日志保留在内存中以便编写断言。

我们将使用 Japa 的 each.setup 钩子来切换进入和退出 raw 模式。

test.group('Commands greet', (group) => {
group.each.setup(() => {
ace.ui.switchMode('raw')
return () => ace.ui.switchMode('normal')
})
// test goes here
})

定义好钩子后,你可以将测试更新如下。

test('should greet the user and finish with exit code 1', async () => {
/**
* Create an instance of the Greet command class
*/
const command = await ace.create(Greet, [])
/**
* Execute command
*/
await command.exec()
/**
* Assert command exited with status code 0
*/
command.assertSucceeded()
/**
* Assert the command printed the following log message
*/
command.assertLog('[ blue(info) ] Hello world from "Greet"')
})

测试表格输出

与测试日志消息类似,你可以通过将 UI 库切换到 raw 模式来为表格输出编写断言。

async run() {
const table = this.ui.table()
table.head(['Name', 'Email'])
table.row(['Harminder Virk', '[email protected]'])
table.row(['Romain Lanz', '[email protected]'])
table.row(['Julien-R44', '[email protected]'])
table.row(['Michaël Zasso', '[email protected]'])
table.render()
}

对于上面的表格,你可以为其编写如下断言。

const command = await ace.create(Greet, [])
await command.exec()
command.assertTableRows([
['Harminder Virk', '[email protected]'],
['Romain Lanz', '[email protected]'],
['Julien-R44', '[email protected]'],
['Michaël Zasso', '[email protected]'],
])

拦截提示

由于提示会阻塞终端以等待手动输入,因此在编写测试时你必须以编程方式拦截并回应它们。

提示使用 prompt.trap 方法进行拦截。该方法接受提示标题(区分大小写),并提供一个可链式调用的 API 来配置额外的行为。

拦截会在提示被触发后自动移除。如果测试在未触发带有拦截的提示的情况下就结束了,将抛出一个错误。

在以下示例中,我们对一个标题为 "What is your name?" 的提示设置拦截,并使用 replyWith 方法来回应它。

const command = await ace.create(Greet, [])
command.prompt
.trap('What is your name?')
.replyWith('Virk')
await command.exec()
command.assertSucceeded()

选择选项

你可以使用 chooseOptionchooseOptions 方法,为单选或多选提示选择选项。

command.prompt
.trap('Select package manager')
.chooseOption(0)
command.prompt
.trap('Select database manager')
.chooseOptions([1, 2])

接受或拒绝确认提示

你可以使用 toggleconfirm 方法来接受或拒绝显示的提示。

command.prompt
.trap('Want to delete all files?')
.accept()
command.prompt
.trap('Want to delete all files?')
.reject()

针对校验进行断言

要测试提示的校验行为,你可以使用 assertPassesassertFails 方法。这些方法接受提示的值,并针对提示的 validate 方法对其进行测试。

command.prompt
.trap('What is your name?')
// assert the prompt fails when an empty value is provided
.assertFails('', 'Please enter your name')
command.prompt
.trap('What is your name?')
.assertPasses('Virk')

以下是一个同时使用断言和回应提示的示例。

command.prompt
.trap('What is your name?')
.assertFails('', 'Please enter your name')
.assertPasses('Virk')
.replyWith('Romain')

可用的断言

以下是命令实例上可用的断言方法列表。

assertSucceeded

断言命令以 exitCode=0 退出。

await command.exec()
command.assertSucceeded()

assertFailed

断言命令以非零的 exitCode 退出。

await command.exec()
command.assertFailed()

assertExitCode

断言命令以特定的 exitCode 退出。

await command.exec()
command.assertExitCode(2)

assertNotExitCode

断言命令以任意 exitCode 退出,但不等于给定的退出码。

await command.exec()
command.assertNotExitCode(0)

assertLog

断言命令使用 this.logger 属性写入了一条日志消息。你可以选择性地断言输出流为 stdoutstderr

await command.exec()
command.assertLog('Hello world from "Greet"')
command.assertLog('Hello world from "Greet"', 'stdout')

assertLogMatches

断言命令写入了一条匹配给定正则表达式的日志消息。

await command.exec()
command.assertLogMatches(/Hello world/)

assertTableRows

断言命令向 stdout 打印了一个表格。你可以以列数组的形式提供表格行。每一列表示为单元格的数组。

await command.exec()
command.assertTableRows([
['Harminder Virk', '[email protected]'],
['Romain Lanz', '[email protected]'],
['Julien-R44', '[email protected]'],
])