跳转到主内容
极星编程网:以代码为星,赴技术山海!

PHP怎么实现Prophecy行为验证_PHP测试替身高级用法【指南】

Prophecy 不直接模拟方法调用,而是通过 $prophet->checkPredictions() 回溯验证声明的预期是否满足:未调用、次数错误或参数不匹配均抛异常;shouldReceive() 仅声明预期,非拦截替换;willBeCalled() 已弃用,应改用 willReturn(null);需手动调用 checkPredictions() 才能触发验证。 Prophecy 不能直接验证“行为”本身,它只验证你声明的预期是否被满足——没调用、调用次数错、参数不匹配,都会在
$prophet->checkPredictions()
(或 PHPUnit 运行结束时)抛出异常。 为什么
shouldReceive()
不是“模拟方法调用” 很多人误以为
shouldReceive('doSomething')
是在“拦截并替换方法”,其实它只是在声明:“等下测试代码里必须调用这个方法,且满足我后续加的条件”。Prophecy 不会提前注入代理对象,也不修改原始类逻辑——它靠的是测试执行完后回溯检查调用记录。 如果你没在测试中真正调用该方法,
checkPredictions()
会报
MethodNotCalledException
如果调用了但参数不匹配(比如期望
['id' => 123]
,实际传了
['id' => 456]
),同样失败
shouldReceive()
后不接
willReturn()
或
willThrow()
是完全合法的——你只关心“是否被调用”,不关心返回值
willBeCalled()
和
willReturn()
的本质区别
willBeCalled()
是空操作桩(no-op stub),仅用于标记“此调用可接受”,不改变返回值;而
willReturn()
明确控制返回结果。两者常被混用,但语义和副作用不同:
->shouldReceive('save')->with($user)->willBeCalled()
:允许调用,返回
null
(PHP 默认),适合只验证流程不依赖返回值的场景
->shouldReceive('save')->with($user)->willReturn(true)
:强制返回
true
,适合下游逻辑依赖返回布尔值做分支判断 若同时写了
willBeCalled()
和
willReturn()
,后者优先,前者被忽略 注意:Prophecy 7+ 已弃用
willBeCalled()
,推荐统一用
willReturn(null)
替代 验证调用顺序和多次调用的陷阱 Prophecy 默认不关心调用顺序,也不区分“第一次”和“第二次”调用——除非你显式用
->shouldBeCalledTimes(2)
或链式限定: 立即学习 “ PHP免费学习笔记(深入) ”; 要验证“先调用
start()
,再调用
commit()
”,得靠测试逻辑组织,Prophecy 本身不提供
shouldBeCalledBefore()
这类断言 验证两次不同参数的调用,必须分开声明:
$repo->shouldReceive('find')->with(1)->willReturn($user1); $repo->shouldReceive('find')->with(2)->willReturn($user2);
否则第二次调用会因未匹配任何预言而失败 如果只写一次
shouldReceive('find')->willReturn(...)->shouldBeCalledTimes(2)
,则两次都必须传相同参数,否则报错 过度使用
shouldBeCalledTimes()
容易让测试脆弱——比如重构时加了个缓存层导致调用次数变少,测试就挂,但业务逻辑未必错 与 PHPUnit 集成时最容易漏掉的一步 Prophecy 对象不是自动注册到 PHPUnit 的预测检查机制里的——你必须手动触发
$prophet->checkPredictions()
,或者确保每个 prophecy 对象都被
$this->prophesize(...)->reveal()
后交由 PHPUnit 管理。常见疏忽: 在
setUp()
里创建 prophecy,但在测试方法里忘了
reveal()
,结果传进被测类的是个 Prophecy\Prophecy\ObjectProphecy 实例,运行时报
Call to undefined method
用了
reveal()
,但没在
tearDown()
或测试末尾调用
checkPredictions()
,导致本该失败的调用验证静默通过 多个 prophecy 共享一个
$prophet
实例时,
checkPredictions()
会一并检查全部——别在中间提前调用,否则后续预言失效 最稳妥的做法:每个测试方法末尾加
$this->prophet->checkPredictions();
,或在
tearDown()
统一处理,但前提是所有 prophecy 都来自同一个
$this->prophet
实例。

相关文章