Symfony 6测试时如何替换MailerInterface服务模拟抛出传输异常
在Symfony 6项目的自定义类中使用Symfony Mailer组件,通过构造函数类型提示实现自动装配注入,业务代码如下:
class MyClass { public function __construct(private readonly MailerInterface $mailer) {} public function sendEmail(): array { // 邮件发送逻辑 try { $this->mailer->send($email); return [ 'success' => true, 'message' => 'Email sent', ]; } catch (TransportExceptionInterface $e) { return [ 'success' => false, 'message' => 'Error sending email: ' . $e, ]; } } }
sendEmail()方法在控制器中调用时所有功能运行正常,现在需要测试TransportException的异常处理逻辑,要求测试场景下mailer可以主动抛出TransportException,但多次尝试均未达到预期效果。
注意:无法通过传入无效邮箱地址触发异常,因为
sendEmail方法本身做了参数校验,仅允许传入合法格式的邮箱地址。
方案1:Mock原生Mailer类
测试代码逻辑为启动内核后从容器获取目标类,再创建Mock的Mailer类替换容器内对应服务:// 启动内核并从容器获取目标类 $container = self::getContainer(); $myClass = $container->get('App\Model\MyClass'); // 创建mock mailer服务 $mailer = $this->createMock(Mailer::class); $mailer->method('send') ->willThrowException(new TransportException()); $container->set('Symfony\Component\Mailer\Mailer', $mailer);失败原因:
Mailer类被声明为final,无法进行Mock。方案2:Mock/存根MailerInterface替换容器内Mailer服务
测试代码如下:// 创建存根mailer服务 $mailer = $this->createStub(MailerInterface::class); $mailer->method('send') ->willThrowException(new TransportException()); $container->set('Symfony\Component\Mailer\Mailer', $mailer);失败结果:代码运行无报错,但mailer未抛出预期异常,说明容器内的mailer服务没有被成功替换。
方案3:自定义始终抛出异常的Mailer实现类替换服务
首先编写实现MailerInterface的测试专用类:// MailerExceptionTester.php <?php namespace App\Tests; use Symfony\Component\Mailer\Envelope; use Symfony\Component\Mailer\Exception\TransportException; use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mime\RawMessage; /** * 调用send方法时始终抛出TransportException */ final class MailerExceptionTester implements MailerInterface { public function send(RawMessage $message, Envelope $envelope = null): void { throw new TransportException(); } }测试中替换服务的代码:
$mailer = new MailerExceptionTester(); $container->set('Symfony\Component\Mailer\Mailer', $mailer);失败结果:和方案2表现完全一致,服务未替换成功,异常未触发。
方案4:直接替换容器内的MailerInterface服务
测试代码如下:$mailer = $this->createMock(MailerInterface::class); $mailer->method('send') ->willThrowException(new TransportException()); $container->set('Symfony\Component\Mailer\MailerInterface', $mailer);报错信息:
Symfony\Component\DependencyInjection\Exception\InvalidArgumentException: The "Symfony\Component\Mailer\MailerInterface" service is private, you cannot replace it.方案5:在services.yaml中将MailerInterface配置为公共服务
配置代码:# services.yaml services: Symfony\Component\Mailer\MailerInterface: public: true报错信息:
Error: Cannot instantiate interface Symfony\Component\Mailer\MailerInterface方案6:为MailerInterface添加公共别名
配置代码:# services.yaml services: app.mailer: alias: Symfony\Component\Mailer\MailerInterface public: true报错信息:
Symfony\Component\DependencyInjection\Exception\InvalidArgumentException: The "Symfony\Component\Mailer\MailerInterface" service is private, you cannot replace it.
如何在测试场景下成功替换自动装配注入的MailerInterface服务,完成异常处理逻辑的测试?
内容的提问来源于stack exchange,提问作者thorndeux

