Salesforceのプロセスビルダーから呼び出すApexのテストのやり方


備忘録として。

プロセスビルダー使っていますか?
弊社リバネスでは、プロセスビルダーからApexを呼び出して、SlackのAPIを叩くみたいな処理を書いています。
この度、新規プロセスを作ったのですが、どうやって本番環境にデプロイしてたっけ??となったので備忘録として書き残しておこうと思います。

やってること

特定のオブジェクトの新規作成・更新時にプロセスビルダーがプロセスを起動します。
こちらは商談の担当事業部に変更があった場合にSlack通知をしようというものです。

プロセスビルダー側から変数をApexクラスに渡して実行してもらう処理です。
Sandboxでテストするのは簡単なのですが、本番デプロイするときにどうすれば・・・?となりました。

失敗パターン

Apexの実行のトリガーになるのはプロセスです。
しかし、本番環境のプロセスには該当する実行条件がありません。
そこで、送信変更セットの中にプロセスも一緒に盛り込んでみました。
送信変更セットには、プロセスも盛り込むことが可能です。フロー定義というところに入っているのでそれを入れましょう。
さて、そうすることによって、テスト前にプロセスがupdateされ、テストが無事に実行されるはずでした。
しかし、そうはなりませんでした。テスト前にプロセスのupdateはされないみたいです。仕方ないですね。

成功パターン

プロセスは関係なく、テスト上でApexクラスが動くようにテストを書きます。
Slackにメッセージを投げるためのクラスはこんな感じです。
InvocableMethodを使います。

SlackGMAlertOpportunityDevChanged.cls
public class SlackGMAlertOpportunityDevChanged{//商談の事業部が変わりました
    private static final String slackURL = 'https://hooks.slack.com/services/xxxxxx';//SlackのincomingWebHookのURL

    public class arguments {
        @InvocableVariable(label='商談ID')
        public String oppID;
        @InvocableVariable(label='クライアント名')
        public String Account;
        @InvocableVariable(label='商談名')
        public String oppName;
        @InvocableVariable(label='商談金額')
        public Integer Amount;
        @InvocableVariable(label='完了予定日')
        public Date closeDate;
        @InvocableVariable(label='フェーズ')
        public String oppPhase;
        @InvocableVariable(label='商談所有者姓')
        public String OwnerLastName;
        @InvocableVariable(label='商談所有者名')
        public String OwnerFirstName;
        @InvocableVariable(label='新商談事業部名')
        public String NewDevName;
        @InvocableVariable(label='旧商談事業部名')
        public String OldDevName;
    }

    @InvocableMethod(label='SlackGMAlertOpportunityDevChanged')
    public static void postToSlack(List<arguments> argumentsList) {
        arguments o = argumentsList[0]; // If bulk, only post first to avoid overloading Slack channel
        //JSONをネストする場合はこちらを参照:https://foobarforce.com/2014/07/25/apex-method-of-the-day-json-serialize-object/
        Map<String,Object> msg = new Map<String,Object>();
        msg.put('text',  + '事業部: * ' + o.OldDevName + '* → *' + o.NewDevName + '* \n商談所有者:  *' + o.OwnerLastName + o.OwnerFirstName + '* \n完了予定日: *' + String.valueOf(o.closeDate) + '* \n売上予定額: * ¥' + o.Amount.format() +  '* \nURL:https://lnest.my.salesforce.com/' + o.oppID + '\nクライアント名: *' + o.Account + '* \nフェーズ: *' + o.oppPhase +'* \n商談名: * ' + o.oppName + '* \n -----------------------');
        msg.put('mrkdwn', true);
        String body = JSON.serialize(msg); 
queueJob(new QueueableSlackCall(slackURL,body));
    }

    public class QueueableSlackCall implements System.Queueable, Database.AllowsCallouts {

        private final String url;
        private final String body;

        public QueueableSlackCall(String url, String body) {
            this.url = url;
            this.body = body;
        }

        public void execute(System.QueueableContext ctx) {
            HttpRequest req = new HttpRequest();
            req.setEndpoint(url);
            req.setMethod('POST');
            req.setBody(body);
            Http http = new Http();
            //参考 https://developer.salesforce.com/forums/?id=906F00000009BO1IAM

            try{
                HTTPResponse res = http.send(req);
                }catch(Exception e){
                }
        }

    }
}

これのテストを書くとこんなふうになります

@isTest
public class SlackGMAlertOpportunityCloseDChangedTest{
    //
    // DML後にHTTP CallOutのテストを行うと
    // "You have uncommitted work pending. Please commit or rollback before calling out"
    // エラーが発生するため、各テストは startTest() - stopTest() でトランザクションを分離します。
    //


    @isTest(SeeAllData=true)
    static void test1() {
//適当な商談を引っ張ってくる
        LIST<Opportunity>opps = new LIST<Opportunity>();
        opps = [SELECT id,hogehoge..... FROM Opportunity WHERE xxxx];
        Opportunity opp = opps[0];
        Test.startTest();
        SlackGMAlertOpportunityDevChanged.arguments args = new SlackGMAlertOpportunityDevChanged.arguments();
        args.oppID = opp.id;
        args.Account = opp.Account.Name;
        args.oppName = opp.Name;
        args.Amount = Integer.valueOf(opp.Amount);
        args.closeDate = opp.closeDate;
        args.oppPhase = '07 契約書面確認中';
        args.OwnerLastName  = 'test';
        args.OwnerFirstName = 'test';
        args.NewDevName = 'ED';
        args.OldDevName = 'HD';
        LIST<SlackGMAlertOpportunityDevChanged.arguments> argsList = new LIST<SlackGMAlertOpportunityDevChanged.arguments>();
        argsList.add(args);
        SlackGMAlertOpportunityDevChanged.postToSlack(opty);
        Test.stopTest();       
    }   
}

こんなイメージです。
InvocableMethodを使ってプロセスから引数を渡す場合は、InvocableVariableを使います。
これをまとめて受け取るカスタムクラスを生成して、それを受け口にする必要があります。
テスト上では、該当するクラスを呼び出して、そこに適当な変数を与え、テストをすればOKということになるようです。
プロセスを必ず絡ませないと駄目なのかとおもって無駄な時間を使ってしまった。