Skip to content

控制台测试

命令行测试是指测试作为应用程序或包代码库一部分的自定义 Ace 命令。

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

基本示例

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

sh
node ace make:command greet
ts
import { BaseCommand } from '@adonisjs/core/ace'
import { CommandOptions } from '@adonisjs/core/types/ace'

export default class Greet extends BaseCommand {
  static commandName = 'greet'
  static description = '通过名称问候用户名'

  static options: CommandOptions = {}

  async run() {
    this.logger.info('来自 "Greet" 的 Hello world')
  }
}

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

sh
node ace make:test commands/greet --suite=unit

# DONE:    create tests/unit/commands/greet.spec.ts

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

ts
import { test } from '@japa/runner'
import Greet from '#commands/greet'
import ace from '@adonisjs/core/services/ace'

test.group('Commands greet', () => {
  test('应该问候用户并以退出码 0 完成', async () => {
    /**
     * 创建 Greet 命令类的实例
     */
    const command = await ace.create(Greet, [])

    /**
     * 执行命令
     */
    await command.exec()

    /**
     * 断言命令以状态码 0 退出
     */
    command.assertSucceeded()
  })
})

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

sh
node ace test --files=commands/greet

测试日志记录器输出

Greet 命令当前将日志消息写入终端。要捕获此消息并为其编写断言,我们需要将 ace 的 UI 库切换到 raw 模式。

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

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

ts
test.group('Commands greet', (group) => {
  // highlight-start
  group.each.setup(() => {
    ace.ui.switchMode('raw')
    return () => ace.ui.switchMode('normal')
  })
  // highlight-end
  
  // 测试在这里
})

定义钩子后,您可以按如下方式更新测试。

ts
test('应该问候用户并以退出码 1 完成', async () => {
  /**
   * 创建 Greet 命令类的实例
   */
  const command = await ace.create(Greet, [])

  /**
   * 执行命令
   */
  await command.exec()

  /**
   * 断言命令以状态码 0 退出
   */
  command.assertSucceeded()

  // highlight-start
  /**
   * 断言命令打印了以下日志消息
   */
  command.assertLog('[ blue(info) ] 来自 "Greet" 的 Hello world')
  // highlight-end
})

测试表格输出

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

ts
async run() {
  const table = this.ui.table()
  table.head(['Name', 'Email'])

  table.row(['Harminder Virk', 'virk@adonisjs.com'])
  table.row(['Romain Lanz', 'romain@adonisjs.com'])
  table.row(['Julien-R44', 'julien@adonisjs.com'])
  table.row(['Michaël Zasso', 'targos@adonisjs.com'])

  table.render()
}

给定上述表格,您可以如下为其编写断言。

ts
const command = await ace.create(Greet, [])
await command.exec()

command.assertTableRows([
  ['Harminder Virk', 'virk@adonisjs.com'],
  ['Romain Lanz', 'romain@adonisjs.com'],
  ['Julien-R44', 'julien@adonisjs.com'],
  ['Michaël Zasso', 'targos@adonisjs.com'],
])

捕获提示

由于提示会阻塞终端等待手动输入,因此在编写测试时必须以编程方式捕获和响应它们。

提示使用 prompt.trap 方法捕获。该方法接受提示标题(区分大小写)并提供可链式 API 来配置其他行为。

提示触发后陷阱会自动移除。如果测试完成时未触发带有陷阱的提示,将抛出错误。

在以下示例中,我们在标题为 "你叫什么名字?" 的提示上放置陷阱,并使用 replyWith 方法回答它。

ts
const command = await ace.create(Greet, [])

// highlight-start
command.prompt
  .trap('你叫什么名字?')
  .replyWith('Virk')
// highlight-end

await command.exec()

command.assertSucceeded()

选择选项

您可以使用 chooseOptionchooseOptions 方法使用 select 或 multi-select 提示选择选项。

ts
command.prompt
  .trap('选择包管理器')
  .chooseOption(0)
ts
command.prompt
  .trap('选择数据库管理器')
  .chooseOptions([1, 2])

接受或拒绝确认提示

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

ts
command.prompt
  .trap('要删除所有文件吗?')
  .accept()
ts
command.prompt
  .trap('要删除所有文件吗?')
  .reject()

断言验证

要测试提示的验证行为,您可以使用 assertPassesassertFails 方法。这些方法接受提示的值,并根据提示的 validate 方法测试它。

ts
command.prompt
  .trap('你叫什么名字?')
  // 断言当提供空值时提示失败
  .assertFails('', '请输入您的名字')
  
command.prompt
  .trap('你叫什么名字?')
  .assertPasses('Virk')

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

ts
command.prompt
  .trap('你叫什么名字?')
  .assertFails('', '请输入您的名字')
  .assertPasses('Virk')
  .replyWith('Romain')

可用断言

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

assertSucceeded

断言命令以 exitCode=0 退出。

ts
await command.exec()
command.assertSucceeded()

assertFailed

断言命令以非零 exitCode 退出。

ts
await command.exec()
command.assertFailed()

assertExitCode

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

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

assertNotExitCode

断言命令以任何 exitCode 退出,但不是给定的退出码。

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

assertLog

断言命令使用 this.logger 属性写入日志消息。您可以选择将输出流断言为 stdoutstderr

ts
await command.exec()

command.assertLog('来自 "Greet" 的 Hello world')
command.assertLog('来自 "Greet" 的 Hello world', 'stdout')

assertLogMatches

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

ts
await command.exec()

command.assertLogMatches(/Hello world/)

assertTableRows

断言命令向 stdout 打印表格。您可以将表格行作为列数组提供。列表示为单元格数组。

ts
await command.exec()

command.assertTableRows([
  ['Harminder Virk', 'virk@adonisjs.com'],
  ['Romain Lanz', 'romain@adonisjs.com'],
  ['Julien-R44', 'julien@adonisjs.com'],
])