我如何在测试期间模拟Symfony邮件程序?

l3zydbqr  于 2023-03-13  发布在  其他
关注(0)|答案(3)|浏览(196)

我在Symfony 6项目的一个自定义类中使用Symfony mailer,通过类的构造函数中的类型提示使用自动装配,如下所示:

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()方法,一切正常。
现在我想测试TransportException是否被正确处理,为此我需要邮件程序在我的测试中抛出TransportException,然而,这并不像我希望的那样工作。

注意:我不能通过传递无效的电子邮件地址来引发异常,因为sendMail方法只允许有效的电子邮件地址。

我尝试过的事情:

1)使用模拟邮件

// boot kernel and get Class from container
$container = self::getContainer();
$myClass = $container->get('App\Model\MyClass');

// create mock mailer service
$mailer = $this->createMock(Mailer::class);
$mailer->method('send')
        ->willThrowException(new TransportException());
$container->set('Symfony\Component\Mailer\Mailer', $mailer);

事实证明,我不能模拟Mailer类,因为它是final

2)使用模拟(或存根)MailerInterface

// create mock mailer service
$mailer = $this->createStub(MailerInterface::class);
$mailer->method('send')
        ->willThrowException(new TransportException());
$container->set('Symfony\Component\Mailer\Mailer', $mailer);

没有错误,但没有抛出异常。似乎邮件服务没有被替换。

3)使用自定义MailerExceptionTester类

// 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;

/**
 * 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('Symfony\Component\Mailer\Mailer', $mailer);

结果与2)相同

4)尝试替换MailerInterface服务而不是Mailer

// create mock mailer service
$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)将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.
如何在测试中替换autowired MailerInterface服务?

46scxncf

46scxncf1#

我正试图做到这一点,我相信我已经找到了一个解决方案的基础上关闭你已经尝试过。
在我的services.yaml中,我重新声明了mailer.mailer服务,并在测试环境中将其设置为public:

when@test:
    services:
        mailer.mailer:
            class: Symfony\Component\Mailer\Mailer
            public: true
            arguments:
                - '@mailer.default_transport'

这个设置应该使Symfony Mailer服务的行为与以前完全相同,但是因为它现在是公共的,所以如果需要,我们可以在容器中覆盖它使用的类。
我复制了你写的自定义Mailer类...

// 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;

/**
 * Always throws a TransportException
 */
final class MailerExceptionTester implements MailerInterface
{
    public function send(RawMessage $message, Envelope $envelope = null): void
    {
        throw new TransportException();
    }
}

......在我的测试代码中,我获得了测试容器,并用异常抛出类的示例替换了mailer.mailer服务:

$mailer = new MailerExceptionTester();
static::getContainer()->set('mailer.mailer', $mailer);

现在,无论在何处注入Mailer服务,使用的类都将是自定义异常抛出类!

izkcnapc

izkcnapc2#

第一次尝试的顺序应该是正确的。

// 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');

未测试,但您将类作为对象获取,因此在模拟之前已将de dependency解析为服务。这应首先替换容器中的服务,然后从容器中获取MyClass
然而,您也可以完全跳过容器的构建,只使用PhpUnit。

$mock = $this->createMock(Mailer::class);
// ... 

$myClass = new MyClass($mock);
$myClass->sendEmail();
bvuwiixz

bvuwiixz3#

对于像我一样发现这个问题并且只想在测试中Assert电子邮件消息的人来说,与其模仿MailerInterface,不如使用内置的MailerAssertionsTrait,它没有模拟传输异常的选项,但是这个特性非常适合邮件内容测试。

// tests/Controller/MailControllerTest.php
namespace App\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class MailControllerTest extends WebTestCase
{
    public function testMailIsSentAndContentIsOk()
    {
        $client = static::createClient();
        $client->request('GET', '/mail/send');
        $this->assertResponseIsSuccessful();

        $this->assertEmailCount(1); // use assertQueuedEmailCount() when using Messenger

        $email = $this->getMailerMessage();

        $this->assertEmailHtmlBodyContains($email, 'Welcome');
        $this->assertEmailTextBodyContains($email, 'Welcome');

        $this->assertNotEmpty($email->getContext()['some_param']);
    }
}

参考文献:

相关问题