環境: CakePHP1.3
ComponentTestCaseを使ってメールの送信をテストする方法をまとめておきます。Qdmailerを使うことを前提としていますが、標準のEmailComponentなど他のコンポーネントでも同じようにテストできると思います。
単純なケース
メール送信をテストする上で最も重要なことは、メールの種類ごとにコンポーネントを作るということです。
まずメール送信コンポーネントに共通の振る舞いを定義するため、以下のような Mailer クラスを app/libs に作ります。 実際のインターフェイスや初期化処理などは自分のアプリケーションに合わせて調整してください。
abstract class Mailer extends Object { var $components = array('Qdmailer.Qdmailer'); var $_controller; var $_settings; function initialize($controller, $settings=array()) { $this->_controller = $controller; $this->_settings = $settings; } function create() { } protected function _rawSend() { $this->Qdmailer->send(); } function send() { $this->Qdmailer->resetHeaderBody(); $args = func_get_args(); $this->dispatchMethod('create', $args); $this->_rawSend(); } }
次にメールの種類に合わせたメール送信用コンポーネントを作ります (他のコンポーネントとの混在を防ぐために components/mailers などサブディレクトリに作成することをおすすめします)。
先ほど作った Mailer クラスを継承して create メソッドを実装します。 メールの送信に使用する値は、create の引数で受け渡すよりは、 コントローラの viewVars 経由で受け取る方が良いと思います(メールテンプレートのレンダリングでも必要になるので)。
App::import('Lib', 'Mailer'); class RegisterRequestMailerComponent extends Mailer { function create() { $email = $this->_controller->viewVars['User']['email']; $q = $this->Qdmailer; $q->resetHeaderBody(); $q->to($email); $q->subject('ユーザー登録が完了しました'); $q->cakeText('', 'register'); } }
単体テストではこの create メソッドをテストします(Mailerクラスに対するテストも書く必要がありますが省略)。 特定の入力(引数、viewVars)に対して宛先、送信元、そして本文などが期待通りに作成できるかどうかをテストします。
class RegisterMailerComponentTest extends ComponentTestCase { var $components = array('RegisterMailer'); function testCreate() { //viewVarsの登録 $data = array('User' => array('id' => 123, 'username' => 'xxxxxx', 'email' => 'abc@example.com'), 'login_url' => 'http://example.com/register'); $this->Controller->set($data); $this->RegisterMailer->create(); $q = $this->RegisterMailer->Qdmailer; //本文 $body = mb_convert_encoding($q->content['TEXT']['CONTENT'], 'utf-8', $q->charset_content); $this->assertTrue(strpos($body, $data['login_url']) !== false); //宛先など $this->assertEqual(1, count($q->to)); $this->assertEqual($data['User']['email'], $q->to[0]['mail']); } }
このコンポーネントを使用するコントローラからは、send メソッドを呼ぶだけです。
class UsersController extends AppController { var $components = array('RegisterMailer'); function finish() { //... if($this->User->save()) { $this->RegisterMailer->send(); } } }
より複雑なケース
次に一度に複数のメールを送る場合について考えてみます。 例えば登録完了メールをユーザと管理者に送るとしましょう。 この場合は「ユーザ宛メール(UserRegisterMailer)」と「管理者宛メール(AdminRegisterMailer)」の他に、 それらをまとめる「RegisterMailer」という3つの Mailer クラスを作成します。
UserRegisterMailer と AdminRegisterMailer は単純な例と同じように作ってテストしてください。 RegisterMailer では $components 配列を使ってそれら2つを読み込み、送信処理を委譲します。
App::import('Lib', 'Mailer'); class RegisterMailerComponent extends Mailer { var $components = array('UserRegisterMailer', 'AdminRegisterMailer'); function sendUserMail() { //viewVarsの加工処理など... $this->UserRegisterMailer->send(); } function sendUserMail() { //viewVarsの加工処理など... $this->AdminRegisterMailer->send(); } function send() { $this->sendUserMail(); $this->sendAdminMail(); } }
このクラスに対するテストはモックを使って行うことができます。 本文や宛先の生成処理は移譲先でテストされているので、 移譲先に対する入力の形式と、適切なメソッドの呼び出しが行われていることをテストすれば十分です。
App::import('Component', 'UserRegisterMailer'); App::import('Component', 'AdminRegisterMailer'); Mock::generate('UserRegisterMailerComponent'); Mock::generate('AdminRegisterMailerComponent'); class RegisterMailerComponentTest extends ComponentTestCase { var $components = array('RegisterMailer'); function startTest($m) { parent::startTest($m); $this->RegisterMailer->UserRegisterMailer = new MockUserRegisterMailerComponent(); $this->RegisterMailer->AdminRegisterMailer = new MockAdminRegisterMailerComponent(); } function testSendUserMail() { $this->Controller->set(array(/* ... */)); $this->RegisterMailer->UserRegisterMailer->expectOnce('send'); $this->RegisterMailer->sendUserMail(); //...$this->Controller->viewVars が適切に加工されていることなどを確認... } function testSend() { $this->RegisterMailer->UserRegisterMailer->expectOnce('send'); $this->RegisterMailer->AdminRegisterMailer->expectOnce('send'); $this->RegisterMailer->send(); } }
このコンポーネントの使い方は「単純な場合」と全く同じで、コントローラは一切変更する必要がありません。
App::build(array(‘components’ => array(COMPONENTS .’/mailers/’)));
は不要ですよー。App::import()は再帰的にディレクトリを探索してくれます。
コメントありがとうございます。
> App::import()は再帰的にディレクトリを探索してくれます。
ずいぶんと気が利いた機能ですね。初めて知りました。
記事の内容も修正しておきました。
[...] MyTestCase はじめとする必要なクラスファイルを読み込む。下の例では ComponentTestCase [...]