Search code examples
symfonytestingswiftmailersymfony-3.4

Test email sending functionality without the need of invoking a controller method


When I looked over this piece of symfony's documentation, I realized that the actual test is being done on a controller.

In my case what I want to test is a custom function in a service for example:

class MyMailService
{

 public function __construct(SwiftMailer $mailer)
 {
   $this->mailer=$mailer;
 }


 public function sendHelloEmail($from,$to)
 {
   $message = (new \Swift_Message('Hello Email'))
        ->setFrom($from)
        ->setTo($to)
        ->setBody('I can see stars');

    $this->mailer->send($message);
 }

}

So how I can test whether the application sends an email without the need to invoke client tests?


Solution

  • As @BentCoder suggested you can use the approach ot having the mails stored in the spool directory and check if message exists. In order to identify use a custom header. In my case I have just places a common name in an X-Agent header.

    My sending email service code is:

    namespace AppBundle\Services;
    
    use AppBundle\Interfaces\EmailSenderInterface;
    use \Swift_Mailer;
    use \Swift_Message;
    
    class NormalEmailSend implements EmailSenderInterface
    {
    
      /**
      * @var Swift_Mailer
      */
      private $mailer=null;
    
      public function __construct(Swift_Mailer $mailer)
      {
        $this->mailer=$mailer;
      }
    
      /**
      * @inheritdoc
      */
      public function send($from,$to,$title="",$bodyPlain="",$bodyHtml="",array $cc=[],array $bcc=[])
      {
    
        $message=new Swift_Message($title);
        $message->setFrom($from)->setTo($to)->setBody($bodyPlain,'text/plain');
    
        if($bodyHtml){
            $message->addPart($bodyHtml,'text/html');
        }
    
        $headers = $message->getHeaders();
        $headers->addTextHeader('X-Agent','ellakcy_member_app');
    
        return $this->mailer->send($message);
      }
    
    
    }
    

    And the Test is:

    namespace Tests\AppBundle\Services;
    
    use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
    use \Swift_Message;
    use Symfony\Component\Filesystem\Filesystem;
    use Symfony\Component\Finder\Finder;
    use Symfony\Component\HttpKernel\KernelInterface;
    
    use AppBundle\Services\NormalEmailSend;
    
    /**
    * @testype functional
    */
    class ContactEmailSendTest extends KernelTestCase
    {
    
       /**
       * @var AppBundle\Services\NormalEmailSend
       */
       private $service;
    
       /**
       * @var String
       */
       private $spoolPath=null;
    
        /**
        * {@inheritDoc}
        */
         protected function setUp()
         {
             $kernel = self::bootKernel();
             $container = $kernel->getContainer();
             $this->service = $container->get(NormalEmailSend::class);
             $this->spoolPath = $container->getParameter('swiftmailer.spool.default.file.path');
         }
    
        public function testSendEmail()
        {
           $from='sender@example.com';
           $to='receiver@example.com';
           $this->service->send($from,$to,'Hello','Hello','Hello');
           $this->checkEmail();
        }
    
        private function checkEmail()
        {
          $spoolDir = $this->getSpoolDir();
          $filesystem = new Filesystem();
    
          if ($filesystem->exists($spoolDir)) {
              $finder = new Finder();
              $finder->in($spoolDir)
                     ->ignoreDotFiles(true)
                    ->files();
    
              if(!$finder->hasResults()){
                $this->fail('No email has been sent');
              }
    
              $counter=0;
              foreach ($finder as $file) {
                  /** @var Swift_Message $message */
                  $message = unserialize(file_get_contents($file));
                  $header = $message->getHeaders()->get('X-Agent');
                  $this->assertEquals($header->getValue(),'ellakcy_member_app');
                  $counter++;
              }
    
              //@todo Possibly Consinder not doing this check
              if($counter===0){
                  $this->fail('No email has been sent');
              }
    
          } else {
            $this->fail('On test environment the emails should be spooled and not actuallt be sent');
          }
        }
    
    
        /**
        * {@inheritDoc}
        */
        public function tearDown()
        {
            $spoolDir = $this->getSpoolDir();
            $filesystem = new Filesystem();
            $filesystem->remove($spoolDir);
        }
    
        /**
        * @return string
        */
        private function getSpoolDir()
        {
          return $this->spoolPath;
        }
    
    }
    

    Hope it helps, also you can modify the code seen above in order to place a tracking number via X-Mail-Id and place it upon your function's sending parameters as an optional one in case that the number is not null you can set it as header.

    So when you traverse the spool directory you can mark emails that are going to be send via its tracking number.