dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

[Salesforce]活動履歴(ActivityHistory)がテストで取得できない?

ややこしかったのでメモ。

はじめに

メール通知を行うクラスのメソッドをテストしたい時、
「送信したメールのアサーションとして何を比較するか」というのにちょっと悩みました。
メールの内容をテストする方法がないので。

テストしたいメソッドの内容はこんな感じです。
(サンプル用に書き直してます)

メール送信クラス

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

リファレンス

Salesforce Developers

Salesforce Developers

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修飾子使ってるもんだから全然知らなかった。