我正在 Symfony 6 項目的自定義類中使用 Symfony 郵件程序。我通過類的構造函數(shù)中的類型提示使用自動裝配,如下所示:
class MyClass { public function __construct(private readonly MailerInterface $mailer) {} public function sendEmail(): array { // Email is sent down here try { $this->mailer->send($email); return [ 'success' => true, 'message' => 'Email sent', ]; } catch (TransportExceptionInterface $e) { return [ 'success' => false, 'message' => 'Error sending email: ' . $e, ]; } } }
在控制器中調用 sendEmail()
方法,一切正常。
現(xiàn)在我想測試 TransportException
s 是否被正確處理。為此,我需要郵件程序在我的測試中拋出 TransportException
s 。然而,這并沒有像我希望的那樣起作用。
注意:我無法通過傳遞無效的電子郵件地址來引發(fā)異常,因為 sendMail
方法只允許有效的電子郵件地址。
我嘗試過的事情:
1) 使用模擬郵件程序
// boot kernel and get Class from container $container = self::getContainer(); $myClass = $container->get('AppModelMyClass'); // create mock mailer service $mailer = $this->createMock(Mailer::class); $mailer->method('send') ->willThrowException(new TransportException()); $container->set('SymfonyComponentMailerMailer', $mailer);
結果我無法模擬 Mailer
類,因為它是 final
。
2)使用模擬(或存根)MailerInterface
// create mock mailer service $mailer = $this->createStub(MailerInterface::class); $mailer->method('send') ->willThrowException(new TransportException()); $container->set('SymfonyComponentMailerMailer', $mailer);
沒有錯誤,但不拋出異常。郵件服務似乎沒有被取代。
3) 使用自定義 MailerExceptionTester 類
// MailerExceptionTester.php <?php namespace AppTests; use SymfonyComponentMailerEnvelope; use SymfonyComponentMailerExceptionTransportException; use SymfonyComponentMailerMailerInterface; use SymfonyComponentMimeRawMessage; /** * Always throws a TransportException */ final class MailerExceptionTester implements MailerInterface { public function send(RawMessage $message, Envelope $envelope = null): void { throw new TransportException(); } }
在測試中:
// create mock mailer service $mailer = new MailerExceptionTester(); $container->set('SymfonyComponentMailerMailer', $mailer);
與 2) 中的結果相同
4)嘗試更換MailerInterface服務而不是Mailer
// create mock mailer service $mailer = $this->createMock(MailerInterface::class); $mailer->method('send') ->willThrowException(new TransportException()); $container->set('SymfonyComponentMailerMailerInterface', $mailer);
錯誤消息:SymfonyComponentDependencyInjectionExceptionInvalidArgumentException:“SymfonyComponentMailerMailerInterface”服務是私有的,您無法替換它。
5) 將 MailerInterface 設置為公開
// services.yaml services: SymfonyComponentMailerMailerInterface: public: true
錯誤:無法實例化接口 SymfonyComponentMailerMailerInterface
6) 為 MailerInterface 添加別名
// services.yaml services: app.mailer: alias: SymfonyComponentMailerMailerInterface public: true
錯誤消息:SymfonyComponentDependencyInjectionExceptionInvalidArgumentException:“SymfonyComponentMailerMailerInterface”服務是私有的,您無法替換它。
如何在測試中替換自動連接的 MailerInterface
服務?
您第一次嘗試的順序應該是正確的。
// boot kernel and get Class from container $container = self::getContainer(); $container->set('Symfony\Component\Mailer\Mailer', $mailer); // create mock mailer service $mailer = $this->createMock(Mailer::class); $mailer->method('send') ->willThrowException(new TransportException()); $myClass = $container->get('App\Model\MyClass');
未經(jīng)測試,但您將類作為對象獲取,因此在模擬之前已經(jīng)解決了對服務的依賴關系。這應該首先替換容器中的服務,然后從容器中獲取MyClass
。
但是,您也可以完全跳過容器的構建。只需使用 PhpUnit。
$mock = $this->createMock(Mailer::class); // ... $myClass = new MyClass($mock); $myClass->sendEmail();
我正在嘗試這樣做,并且我相信我已經(jīng)根據(jù)您已經(jīng)嘗試過的方法找到了解決方案。
在我的 services.yaml
中,我重新聲明 mailer.mailer
服務,并在測試環(huán)境中將其設置為公共:
when@test: services: mailer.mailer: class: Symfony\Component\Mailer\Mailer public: true arguments: - '@mailer.default_transport'
此設置應該使 Symfony Mailer 服務的行為方式與以前完全相同,但是因為它現(xiàn)在是公共的,所以如果需要,我們可以覆蓋它在容器中使用的類。
我復制了您編寫的自定義 Mailer 類...
// MailerExceptionTester.php...在我的測試代碼中,我獲取了測試容器并將
mailer.mailer
服務替換為異常拋出類的實例:$mailer = new MailerExceptionTester(); static::getContainer()->set('mailer.mailer', $mailer);現(xiàn)在,無論注入 Mailer 服務,所使用的類都將是自定義異常拋出類!