phpunit 的 createmock() 用于轻量级接口模拟,必须 mock 接口而非具体类,且需用 expects() 明确声明期望行为,否则未定义方法调用将静默返回 null。

怎么用 createMock() 替换真实服务依赖
PHPUnit 的 createMock() 是 Symfony 测试中最轻量、最常用的 Mock 手段,适用于接口明确、行为简单、不涉及复杂状态的服务。它不执行真实逻辑,只响应你预设的调用规则。
常见错误是直接 mock 类而非接口——比如 $this->createMock(UserRepository::class),但该类没实现接口时,后续注入到类型约束为 UserRepositoryInterface 的构造函数会报错。
- 务必 mock 接口(如
UserRepositoryInterface),而不是具体类,除非该类被显式声明为 final 且无接口 - mock 后必须用
expects()明确声明期望行为,否则调用未定义方法会静默返回null,导致测试通过但逻辑实际失效 - 如果服务方法有多个重载签名(如可选参数),需用
$this->anything()或$this->isType()等匹配器覆盖,避免因参数数量/类型不匹配导致断言失败
什么时候该用 getMockBuilder() 而不是 createMock()
当你要 mock 的类没有接口、或需要控制构造函数行为(比如跳过初始化逻辑)、或要部分 mock(即只 mock 某几个方法,其余走真实逻辑)时,getMockBuilder() 是唯一选择。
典型场景:测试一个封装了 Symfony\Component\HttpClient\HttpClient 的客户端类,你想保留其内部重试逻辑,只 mock 底层 request() 调用。
- 用
getMockBuilder(YourApiClient::class)->disableOriginalConstructor()->getMock()避免构造时触发真实 HTTP 初始化 - 用
setMethods(['request'])(PHP 7.5+ 改为onlyMethods())限定仅 mock 指定方法,其余保持原行为 - 注意:PHP 8.0+ 中
setMethods()已废弃,必须改用onlyMethods()或addMethods(),否则测试会静默忽略 mock 规则
MockResponse 和 JsonMockResponse 怎么配合 HttpClient 测试
别在测试里 new 一个真实 HttpClient——它会发真实请求。正确做法是把 MockResponse 注入到 client 构造中,或替换 service container 中的 http_client 服务。
错误示范:$client = HttpClient::create(); $response = $client->request('GET', 'https://api.example.com'); —— 这会让测试变慢、不可靠、还可能触发限流。
- 用
MockResponse::fromFile(__DIR__.'/Fixtures/api_user_200.json', ['http_code' => 200])加载预存响应,比硬编码字符串更易维护 -
JsonMockResponse是MockResponse的语法糖,自动设置Content-Type: application/json并 JSON-encode 数组体,适合 API 响应测试 - 若要模拟网络异常(如超时、连接拒绝),不能只靠
MockResponse;得用MockHttpClient(Symfony 6.2+)或手动抛出TransportException的 mock
Mock EventDispatcher 时为什么 dispatch() 断言总失败
因为事件对象通常是新实例(new UserRegisteredEvent($user)),而默认的 $this->equalTo() 匹配器只比较值,不比较对象身份。直接写 ->with($event, 'user.registered') 几乎必失败。
正确方式是用对象约束器捕获参数并验证关键属性:
- 用
$this->callback(fn ($arg) => $arg instanceof UserRegisteredEvent && $arg->getUser() === $expectedUser)精确判断事件内容 - 或用
$this->isInstanceOf(UserRegisteredEvent::class)+$this->attributeEqualTo('userId', 123)(需 PHPUnit 10.5+) - 注意:如果事件监听器是异步注册(如通过
addListener()动态绑定),确保测试前已注册,否则dispatch()调用不会触发任何监听器
Mock 的核心不是“让代码不报错”,而是让测试能精确控制输入、可靠捕获输出、并暴露隐式依赖。最容易被忽略的一点:所有 mock 都该有明确的 expects() 声明,哪怕只是 $mock->expects($this->once())——没有期望的 mock,等于没测。











