在 Symfony 4 中对自定义服务做单元测试时,应通过接口依赖并用 PHPUnit createMock 创建模拟对象注入被测服务,验证返回值及调用次数、参数,避免启动 Kernel 或过度 mock。

在 Symfony 4 中对自定义服务做单元测试时,Mock 依赖服务的核心目标是:让被测服务只专注自身逻辑,不触发真实依赖(如 API 调用、数据库读写、邮件发送等)。这需要结合 PHPUnit + PHPUnit\Framework\TestCase(非 WebTestCase),并借助 Mockery 或 PHPUnit 自带的 createMock() 来构造模拟对象,再通过构造函数或 setter 注入到被测服务中。
明确被测服务与依赖接口
确保你的自定义服务通过接口依赖外部服务,而不是具体类。这是可测试性的基础:
- 定义接口:
interface PaymentGatewayInterface { public function charge(string $amount): bool; } - 实现类:
class StripePaymentGateway implements PaymentGatewayInterface { ... } - 被测服务依赖接口:
class OrderProcessor { private PaymentGatewayInterface $gateway; public function __construct(PaymentGatewayInterface $gateway) { $this->gateway = $gateway; } }
使用 PHPUnit createMock 创建依赖模拟
在测试方法中创建接口的 Mock 实例,并预设行为:
- 模拟成功调用:
$mockGateway = $this->createMock(PaymentGatewayInterface::class); $mockGateway->method('charge')->willReturn(true); - 模拟失败场景:
$mockGateway->method('charge')->willReturn(false); - 模拟异常:
$mockGateway->method('charge')->willThrowException(new \RuntimeException('Network error')); - 注入并测试:
$processor = new OrderProcessor($mockGateway); $this->assertTrue($processor->processOrder('ORD-001'));
验证依赖是否被正确调用
除了返回值,还需确认被测服务是否按预期使用了依赖:
- 检查调用次数:
$mockGateway->expects($this->once())->method('charge')->with('100.00'); - 匹配参数类型:
->with($this->isType('string')) - 使用回调验证复杂参数:
->with($this->callback(fn($arg) => is_array($arg) && isset($arg['currency']))))
避免常见陷阱
单元测试应轻量、快速、无副作用:
- 不要在单元测试中启动 Kernel 或访问容器 —— 那属于功能/集成测试范畴
- 不要 mock 最终类或 final 方法(除非用 Mockery 的
makePartial()等高级特性) - 避免过度 mock:只 mock 直接依赖,不 mock 依赖的依赖(即不 mock “依赖的依赖”)
- 不 mock 数据传输对象(DTO)、实体(Entity)或简单值对象 —— 它们不是行为载体


















