Laravel测试Artisan命令需用expectsQuestion预设交互输入,严格匹配提示字符串(含空格标点),并按顺序链式调用;输出断言用getDisplay()获取带ANSI码的原始内容,配合strip_tags()或正则清理后验证;assertExitCode(0)失效常因未捕获异常、手动exit或构造函数报错导致命令未执行到结尾。

如何用 expectsQuestion 模拟用户输入
Artisan 命令里用了 $this->ask()、$this->confirm() 等交互方法时,直接跑测试会卡住。Laravel 提供了 expectsQuestion 来预设回答,但要注意它只匹配「完全一致的提示字符串」——包括空格和标点。
-
expectsQuestion必须在artisan()调用前链式调用,顺序错就无效 - 如果命令里用了
$this->ask('Name? ', 'default'),测试中必须写expectsQuestion('Name? ', 'alice'),末尾空格不能少 - 对
$this->confirm('Continue?'),传true或false即可,但字符串提示仍要一字不差 - 多个交互按出现顺序依次调用
expectsQuestion,不能跳过中间某一个
public function test_it_prompts_for_name_and_email()
{
$this->artisan('make:user')
->expectsQuestion('Name? ', 'Taylor')
->expectsQuestion('Email? ', 'taylor@laravel.com')
->assertExitCode(0);
}
如何捕获并断言命令输出内容
光看退出码不够,常需验证是否打印了预期文本。Laravel 的 artisan() 测试方法默认不返回输出,得用 run() + getDisplay() 手动抓取。
- 不要用
assertSee(),那是 HTTP 测试用的;Artisan 命令测试里得靠getDisplay() -
getDisplay()返回带 ANSI 转义符的原始输出,含颜色控制符(如\x1B[32m),断言前建议用strip_tags()或正则清理 - 若命令输出含动态值(如时间戳、UUID),改用
assertStringContainsString()或正则匹配关键静态部分 - 错误输出走
getErrorOutput(),比如php artisan invalid:command的报错信息
$output = $this->artisan('list')->run();
$this->assertStringContainsString('make:user', $output->getDisplay());
为什么 assertExitCode(0) 有时不生效
assertExitCode() 看似简单,但实际失败往往不是逻辑错,而是命令提前异常终止或没真正执行到结尾。
- 命令里抛出未捕获异常(如数据库连接失败)会导致退出码为 1,但测试可能因异常中断而根本没走到
assertExitCode - 用了
exit()或die()—— Artisan 命令严禁手动 exit,应改用return self::FAILURE - 在
handle()外部(比如构造函数)抛异常,命令甚至不会进入执行流程,assertExitCode不会被触发 - 测试中调用的是命令实例而非 shell 进程,所以
exec('php artisan ...')这种绕过框架的方式会让所有内置断言失效
交互+输出+退出码要一起测才真实
单测某个环节容易漏掉组合问题。比如用户输错格式后命令打印错误提示并退出 1,这种场景必须三者联动验证。
- 先用
expectsQuestion给非法输入(如邮箱不带 @) - 再用
getDisplay()确认错误文案出现 - 最后用
assertExitCode(1)确保没静默成功 - 注意:如果命令内部用
$this->error()输出,它也出现在getDisplay()里,不是getErrorOutput()
最易忽略的是 ANSI 颜色字符干扰断言,以及提示字符串里隐藏的不可见空格 —— 这类细节不打日志根本看不出哪里不匹配。










