ややこしかったのでメモ。
はじめに
メール通知を行うクラスのメソッドをテストしたい時、
「送信したメールのアサーションとして何を比較するか」というのにちょっと悩みました。
メールの内容をテストする方法がないので。
テストしたいメソッドの内容はこんな感じです。
(サンプル用に書き直してます)
メール送信クラス
public class SendEmail { private static final String SUBJECT = 'Sample Email'; private static final String BODY = 'Hi, This is a sample email.'; public void send(Id targetObjId) { Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage(); // 件名、本文、送信先をセット mail.setSubject(SUBJECT); mail.setPlainTextBody(BODY); mail.setTargetObjectId(targetObjId); // 活動履歴に残す mail.setSaveAsActivity(true); // 送信 Messaging.sendEmail(new List<Messaging.SingleEmailMessage> { mail }); } }
で、今回のメールは活動履歴(ActivityHistory
)に残すという設定にしているため
「メールが正しく送信されたかどうか」をAcitivityHistoryオブジェクトの有無で判断しようとしたわけです。
テストはこんなの。
テストクラス
@isTest public class SendEmailTest { public static testMethod void testSend() { // メールの送信先になる Lead leadToSend = new Lead( Firstname = 'Akin', Lastname = 'Kristen', Company = 'Aethna Home Products', Email = 'a.kristen@example.jp' ); insert leadToSend; Test.startTest(); SendEmail mail = new SendEmail(); mail.send(leadToSend.id); Test.stopTest(); // assertion Lead result = [ SELECT Id, ( SELECT WhoId FROM ActivityHistories ) FROM Lead WHERE Id = :leadToSend.id ]; // リードの活動履歴に追加されていることを確認 System.assertEquals(1, result.activityHistories.size()); System.assertEquals(leadToSend.id, result.activityHistories[0].whoId); } }
テストの結果
上記のテストを実行した結果がこちらです。
System.AssertException: Assertion Failed: Expected: 1, Actual: 0
というわけで、ActivityHistory
には何も作られてない...おかしい...
解決策
StackExchangeに解決方法が。
テストをSeeAllData=true
で実行したら成功するようになりました。
@isTest(SeeAllData=true) // これ!!! public class SendEmailTest { public static testMethod void testSend() { // メールの送信先になる Lead leadToSend = new Lead( Firstname = 'Akin', Lastname = 'Kristen', Company = 'Aethna Home Products', Email = 'a.kristen@example.jp' ); insert leadToSend; Test.startTest(); SendEmail mail = new SendEmail(); mail.send(leadToSend.id); Test.stopTest(); // assertion Lead result = [ SELECT Id, ( SELECT WhoId FROM ActivityHistories ) FROM Lead WHERE Id = :leadToSend.id ]; // リードの活動履歴に追加されていることを確認 System.assertEquals(1, result.activityHistories.size()); System.assertEquals(leadToSend.id, result.activityHistories[0].whoId); } }
でもなぜ?
せっかくなので色々やってみる
上の記事とこちらの記事を参考にすると、
ViewAllData
権限がないユーザでテストを実行しているのが原因かもしれない- 通常のSOQLの代わりにDynamic SOQL(
Database.query
)を利用するとうまくいく
などの情報も。
なので、以下の2つも試してみましたが、いずれも失敗。
テスト失敗例その2(Database.queryを使ってみる)
public static testMethod void testSend_fail2() { // メールの送信先になる Lead leadToSend = new Lead( Firstname = 'Akin', Lastname = 'Kristen', Company = 'Aethna Home Products', Email = 'a.kristen@example.jp' ); insert leadToSend; Test.startTest(); SendEmail mail = new SendEmail(); mail.send(leadToSend.id); Test.stopTest(); // assertion ID whereId = leadToSend.id; // Database.queryを使って取得してみる Lead result = (Lead) Database.query(String.join(new List<String> { 'SELECT', 'Id,', '( SELECT WhoId FROM ActivityHistories )', 'FROM', 'Lead', 'WHERE', 'Id = :whereId' }, ' ')); System.assertEquals(1, result.activityHistories.size()); System.assertEquals(leadToSend.id, result.activityHistories[0].whoId); }
テスト失敗例その3(ViewAllData権限を持つユーザで実行する)
public static testMethod void testSend_fail3() { // ViewAllData権限を持つユーザを取得 Profile p = [SELECT Id FROM Profile WHERE PermissionsViewAllData = true LIMIT 1]; User u = new User( Alias = 'sample', Email = 'sample.user@example.jp', EmailEncodingKey = 'UTF-8', LastName = 'Testing', LanguageLocaleKey = 'ja', LocaleSidKey = 'ja_JP', ProfileId = p.Id, TimeZoneSidKey = 'Asia/Tokyo', UserName = 'standarduser@dackdive.com' ); ID whereId; Lead result; System.runAs(u) { // メールの送信先になる Test.startTest(); Lead leadToSend = new Lead( Firstname = 'Akin', Lastname = 'Kristen', Company = 'Aethna Home Products', Email = 'a.kristen@example.jp' ); insert leadToSend; whereId = leadToSend.id; SendEmail mail = new SendEmail(); mail.send(leadToSend.id); Test.stopTest(); // assertion result = [ SELECT Id, ( SELECT WhoId FROM ActivityHistories ) FROM Lead WHERE Id = :whereId ]; } System.assertEquals(1, result.activityHistories.size()); System.assertEquals(whereId, result.activityHistories[0].whoId); }
うーん、イマイチ挙動がわかりません。
解決策その2
SeeAllData=true
を使えばいいことはわかりましたが
テストの内容によってはこれを使うのが難しい時がありますよね。
(レコード数のアサーションなどを行っている場合)
で、どうすればいいかとまた調べた結果
ActivityHistory
のかわりにTask
を使えばよさそうです。
Taskを使ったアサーション
public static void testSend_success2() { // メールの送信先になる Lead leadToSend = new Lead( Firstname = 'Akin', Lastname = 'Kristen', Company = 'Aethna Home Products', Email = 'a.kristen@example.jp' ); insert leadToSend; Test.startTest(); SendEmail mail = new SendEmail(); mail.send(leadToSend.id); Test.stopTest(); // assertion List<Task> results = [ SELECT WhoId FROM Task ]; System.assertEquals(1, results.size()); System.assertEquals(leadToSend.id, results[0].whoId); }
というわけで、メールのテストはTask
を使いましょうという結論になりました。
GitHub
https://github.com/zaki-yama/force_com/tree/master/activity_history_verification
リファレンス
Unit Tests: create completed tasks for ActivityHistory? - Salesforce Stack Exchange
apex - How do I write test for a Histories Inner query? - Salesforce Stack Exchange
プチ情報
SeeAllData=true
ってクラス単位でなくメソッド単位で指定できるんだなあと初めて知りました。
@isTest public class MyTestClass { // こちらは組織の既存データにアクセスできない @isTest public static void testMethod1() { ... } // こちらは組織の既存データにアクセスできる @isTest(SeeAllData=true) public static void testMethod2() { ... } }
普段testMethod
修飾子使ってるもんだから全然知らなかった。