本稿ではCakePHP3のEmailをSMTPサーバなしにPHPUnitで単体テストする方法を紹介する。
最新のCakeならCake\TestSuite\EmailTrait
を使うといい
CakePHPの公式ドキュメントによれば、メールのテストはCake\TestSuite\EmailTrait
を使えば良いらしい。しかし、これはCakePHP3.7以降の話だ。3.7は2018/12/09にリリースされたばかりなので、手元の環境ではこれが使えない。
CakePHP3.7未満の対処法
代わりに使えるのはCakePHP3.3.3で追加されたCake\TestSuite\EmailAssertTrait
なわけだが、これはEmail
のモックを生成するものなので、Email
をnew
しているコードのテストはできない。
ではテストできないのかというとそうではなく、EmailTransport
をSMTPからテスト用のものに差し替えれば、new Email
しているコードもテストすることができる。参照: Cakephp3のTestでメールを送りたくない - Qiita
CakeMailerTestCase
を作る
トランスポートの差し替えは、基本的にsetUp
で
Email::dropTransport('default');
Email::setConfigTransport('default', $テスト用のTransport);
し、tearDown
でもとのトランスポートに戻してあげればいいわけだ。
だが、テストクラスごとに毎回setUp
とtearDown
を書くのは面倒なので、次のCakeMailerTestCase
のようなメールテスト機能を持ったテストクラスを作っておくと便利だ:
<?php
declare(strict_types=1);
namespace App\Test;
use Cake\Mailer\AbstractTransport;
use Cake\Mailer\Email;
use Cake\TestSuite\EmailAssertTrait;
use Cake\TestSuite\TestCase;
abstract class CakeMailerTestCase extends TestCase
{
use EmailAssertTrait;
/**
* @var array
*/
private $originalEmailTransport;
public function setUp(): void
{
parent::setUp();
$this->originalEmailTransport = Email::getConfigTransport('default');
Email::dropTransport('default');
Email::setConfigTransport('default', $this->getTestTransport());
}
public function tearDown(): void
{
parent::tearDown();
Email::dropTransport('default');
Email::setConfigTransport('default', $this->originalEmailTransport);
}
private function getTestTransport(): AbstractTransport
{
return new class($this->getSentEmailSetter()) extends AbstractTransport {
/**
* @var callable
*/
private $setSentEmail;
public function __construct(callable $setSentEmail)
{
parent::__construct();
$this->setSentEmail = $setSentEmail;
}
public function send(Email $email): void
{
($this->setSentEmail)($email);
}
};
}
private function getSentEmailSetter(): callable
{
return function (Email $email): void {
$this->_email = $email;
};
}
}
CakeMailerTestCase
を使ったテストコードは次のようになる:
<?php
declare(strict_types=1);
namespace App\Test;
use Cake\Mailer\Email;
final class CakePhpEmailSenderTest extends CakeMailerTestCase
{
public function test_send_email(): void
{
$email = new Email();
$email->setFrom(['alice@example.com' => 'Alice Brown'])
->setTo('bob@example.com')
->setSubject('Hi')
->send('Hi there.');
$this->assertEmailFrom('alice@example.com', 'Alice Brown');
$this->assertEmailTo('bob@example.com');
$this->assertEmailSubject('Hi');
$this->assertEmailTextMessageContains('Hi there.');
}
}
CakeMailerTestCase
はCake\TestSuite\EmailAssertTrait
を使っているので、assertEmailFrom
などのメール系アサーションも使えるようになっている。