I am using the Symfony mailer in a custom class in a Symfony 6 project. I am using autowiring through type hinting in the class's constructor, like so:
    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,
                ];
            }
        }
    }
The sendEmail() method is called in a controller and everything works fine.
Now I want to test that TransportExceptions are handled correctly. For that I need the mailer to throw TransportExceptions in my tests. However, that does not work as I had hoped.
Note: I cannot induce an exception by passing an invalid email address, as the sendMail method will only allow valid email addresses.
Things I tried:
1) Use mock Mailer
// 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);
Turns out I cannot mock the Mailer class, as it is final.
2) Use mock (or stub) MailerInterface
// create mock mailer service
$mailer = $this->createStub(MailerInterface::class);
$mailer->method('send')
        ->willThrowException(new TransportException());
$container->set('Symfony\Component\Mailer\Mailer', $mailer);
No error, but does not throw an exception. It seems the mailer service is not being replaced.
3) Use custom MailerExceptionTester class
// 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();
    }
}
And in the test:
// create mock mailer service
$mailer = new MailerExceptionTester();
$container->set('Symfony\Component\Mailer\Mailer', $mailer);
Same result as in 2)
4) Try to replace the MailerInterface service instead of Mailer
// create mock mailer service
$mailer = $this->createMock(MailerInterface::class);
$mailer->method('send')
        ->willThrowException(new TransportException());
$container->set('Symfony\Component\Mailer\MailerInterface', $mailer);
Error message: Symfony\Component\DependencyInjection\Exception\InvalidArgumentException: The "Symfony\Component\Mailer\MailerInterface" service is private, you cannot replace it.
5) Set MailerInterface to public
// services.yaml
services:
    Symfony\Component\Mailer\MailerInterface:
        public: true
Error: Cannot instantiate interface Symfony\Component\Mailer\MailerInterface
6) Add alias for MailerInterface
// services.yaml
services:
    app.mailer:
        alias: Symfony\Component\Mailer\MailerInterface
        public: true
Error message: Symfony\Component\DependencyInjection\Exception\InvalidArgumentException: The "Symfony\Component\Mailer\MailerInterface" service is private, you cannot replace it.
How can I replace the autowired MailerInterface service in my test?
 
     
     
    