プログラミング - THIS IS IT !

より良い開発をすべく日々奮闘しているプログラマーのブログです。設計に興味があります。主にPHPネタを書いてます 日本Symfonyユーザー会

Symfonyでメールのインテグレーションテストを行う

f:id:okapon_pon:20151205123050p:plain

この記事はSymfony Advent Calender 2018 15日目の記事です。

はじめに

Symfonyでメールのテストを行う方法ですが、公式ドキュメントにもある通りファンクショナルテスト内でプロファイラーを利用したメールのテストは行うことができます。

symfony.com

しかしコントローラのテストまで行うのはややオーバスペックであり、メール単体でテストを行いたいという場合もあると思います。

特にtwigでメール本文を記述していてif文で表示の出し分けを行っていたりすると、実際にメールを送るのは大変ですし簡単にテストコードが書けたらいいなと思うケースはあるのではないかと思います。

本記事はそのようなケースで参考になる内容となっているのではないかと思います。

実は以前似たような記事を書いたことがありました。

以前の記事 okapon-pon.hatenablog.com

しかしながら、MailServiceを継承する方法は制約も多く実用性に欠けますし、もっとスマートな方法があったため改めてご紹介したいと思います。

メールの検証に Swift_Plugins_MessageLogger を使う

まずはじめに、この内容はSymfony3.4ベースです。

Symfonyを使っている方なら既に知っていると思いますが、メールクラスの実態は \Swift_Message クラスですね。 以降 \Swift_Massge クラスを Messge クラスと省略させてもらいます。

プロファイラーを利用したテストはできると先程書きましたがプロファイラーは送信したMessageクラスを保持しているので、そこからMessageクラスを取得してきてメールの検証を行うことができます。

そのプロファイラーがどうやってMessageクラスの情報を保持してきているかというと、MessageDataCollector クラスを通して情報を収集していて DataCollector はさらに \Swift_Plugins_MessageLogger クラスから情報を集めます。

Swift_Mailer クラスからMessageが送信されるタイミングで \Swift_Plugins_MessageLogger クラスはMessageを保持するようになっています。

これらはSymofnyの環境がdevもしくはtestの場合に行われます。

そのため既にMessageクラスを収集している、この Swift_Plugins_MessageLogger を利用すれば簡単にメールの検証を行うことができます。

以下では実際の利用コードを示します。

サンプルコード

Symfony3.4

<?php

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class MyMailTest extends WebTestCase
{
    public function testSendMail()
    {
        $kernel = self::bootKernel();
        $mailer = $kernel->getContainer()->get('mailer');
        $logger = $kernel->getContainer()->get('swiftmailer.mailer.default.plugin.messagelogger');

        // 実際は実コード側でメール送信を行うが、ここでは分かりやすくテストコード中に記述してます
        $message = new \Swift_Message();
        $message->setTo('to@example.com', 'Aさん');
        $message->setFrom('system@example.com');
        $message->setBody('本文です。');
        $mailer->send($message);

        // メールを検証します
        $this->assertSame(1, $logger->countMessages());
        /** @var \Swift_Message $message */
        $message = $logger->getMessages()[0];
        $this->assertSame(['to@example.com' => 'Aさん'], $message->getTo());
        $this->assertContains('本文です', $message->getBody());
    }
}

未確認ですが、Symfony4.1以降であればprivateなサービスクラスも取得できる方法があるため以下のようなコードでも動くと思います。

<?php

class MyMailTest extends WebTestCase
{
    public function testSendMail()
    {
        self::bootKernel();

        // gets the special container that allows fetching private services
        $container = self::$container;

        $mailer = $container->get(\Swift_Mailer::class);
        $logger = $container->get(Swift_Plugins_MessageLogger::class);

         // ...略
    }
}

参考

New in Symfony 4.1: Simpler service testing (Symfony Blog)

このサンプルコードだけ見ればどうということはないのですが、自分はここに到達するまでにわりと遠回りしてしまってました。。。

SwiftMailerBundleのリファレンスにloggingという項目があり、そこからヒントを得ました。 Mailer Configuration Reference (SwiftmailerBundle) (Symfony 3.4 Docs)

まとめ

ファンクショナルテスト(Controllerのテスト)でなくてもサービスコンテナを用いたメールのインテグレーションテストを簡単に書くことができます。

メールのテストを行う場合、ブラウザをポチポチして実メールを送る必要もなくテストコードでさくっと検証できるので開発がはかどります。

以上です。