PHP
CakePHP
cakephp3

CakePHP3 でメール送信をテストする

More than 1 year has passed since last update.

やりたいこと

テストコードで Cake\Mailer\Email::send() で送信したメールを確認したいのです。

環境

  • CakePHP version 3.3.13
  • PHPUnit version 5.7.21

ざっくりした仕組み

CakePHPでは Cake\Mailer\Transport\DebugTransport があり、e-mailのトランスポートにこれを指定します。そうすると、Cake\Mailer\Email::send() の戻り値で送信内容を得られます。

しかし、send() の戻り値を使ってテストコードを書くのは違うので、send()の引数に渡された Cake\Mailer\Email を捕まえてテストできる状態にします。

コード

TestableTransport.php

DebugTransport を継承して、send() の引数を staticなプロパティに貯めておきます。貯めたものは sendMailLst() で取得できます。

tests/Mailer/Transport/TestableTransport.php
namespace App\Test\Mailer\Transport;

use Cake\Mailer\Transport\DebugTransport;
use Cake\Mailer\Email;

/**
 * Class TestableTransport
 * @package Mailer\Transport
 */
class TestableTransport extends DebugTransport
{
    /** @var array 送信したメール */
    private static $mailList = [];

    /**
     * 送信済みメールをリセットします。
     */
    public static function reset()
    {
        static::$mailList = [];
    }

    /**
     *
     * @return array 送信したメールのリスト
     */
    public static function sendMailLst()
    {
        return static::$mailList;
    }

    /**
     * @inheritdoc
     */
    public function send(Email $email)
    {
        static::$mailList[] = clone $email;
        return parent::send($email);
    }

}

phpunit.xml.dist

テストクラスにトラスポートを指定するコードを書くことができますが、環境変数 CAKEPHP_ENV にPHPUnit用のコンフィグレーションを指定します。

phpunit.xml.dist
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
        colors="true"
        processIsolation="false"
        stopOnFailure="false"
        syntaxCheck="false"
        bootstrap="./tests/bootstrap.php"
>
    <php>
        <ini name="memory_limit" value="-1"/>
        <ini name="apc.enable_cli" value="1"/>

        <env name="CAKEPHP_ENV" value="phpunit"/>

    </php>

    <!-- Add any additional test suites you want to run here -->
    <testsuites>
        <testsuite name="App Test Suite">
            <directory>./tests/TestCase</directory>
        </testsuite>
        <!-- Add plugin test suites here. -->
    </testsuites>

    <!-- Setup a listener for fixtures -->
    <listeners>
        <listener
                class="\Cake\TestSuite\Fixture\FixtureInjector"
                file="./vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureInjector.php">
            <arguments>
                <object class="\Cake\TestSuite\Fixture\FixtureManager" />
            </arguments>
        </listener>
    </listeners>

    <!-- Ignore vendor tests in code coverage reports -->
    <filter>
        <whitelist>
            <directory suffix=".php">./src/</directory>
            <directory suffix=".php">./plugins/*/src/</directory>
        </whitelist>
    </filter>
</phpunit>

config/environments/phpunit.php

PHPUnit用のコンフィグレーションで e-mailのトランスポートに TestableTransport を指定します

config/environments/phpunit.php
    'EmailTransport' => [
        'default' => [
            'className' => 'App\Test\Mailer\Transport\TestableTransport',
        ],
    ],

コントローラー

/**
 * メール送信を行うアクション
 */
public function action()
{
// 〜 省略 〜
    $mail = new Email();

    $mail->from('form@example.co.jp');
    $mail->to('to@example.co.jp');
    $mail->subject('mail subject');
    $mail->send('mail message');
// 〜 省略 〜
}

テストコード

Cake\TestSuite\IntegrationTestCase の子クラスでテスト対象のアクションを実行します。

class XXControllerTest extends IntegrationTestCase
{ 
   public function setUp()
    {
        parent::setUp();
        // 送信したメールをリセットする
        TestableTransport::reset();
    }

    public function testMail()
    {
        // メール送信を行うアクションを実行する
        $this->post('/path/to/action', $params);

        // 送信したメールを確認する
        $mails = TestableTransport::sendMailLst();
        $this->assertEquals( 1 , count($mails), '送信したメールが1件だけ存在する');

        /** @var Cake\Mailer\Email $mail */
        $mail = $mails[0];
        $this->assertEquals( ['form@example.co.jp' =>'form@example.co.jp'] , $mail->from(), '差出人');
        $this->assertEquals( ['to@example.co.jp' =>'to@example.co.jp'] , $mail->to(), '送信先');
        $this->assertEquals( 'mail subject' , $mail->subject(), '件名');
        $this->assertEquals( 'mail message', $mail->message(Email::MESSAGE_TEXT), 'メッセージ');
    }
}