Apex REST コールアウト

学習の目的

このモジュールを完了すると、次のことができるようになります。
  • コールアウトを実行して、外部サービスからデータを受信する。
  • コールアウトを実行して、外部サービスにデータを送信する。
  • 疑似コールアウトを使用して、コールアウトをテストする。

HTTP とコールアウトの基本

REST コールアウトは、HTTP に基づいています。HTTP についていくつか理解しておくことで、コールアウトのしくみを理解しやすくなります。各コールアウト要求は、HTTP メソッドおよびエンドポイントに関連付けられています。HTTP メソッドは、目的のアクション種別を示します。
外部サービスへの Apex コールアウト

最も単純な要求は GET 要求 (GET は HTTP メソッド) です。GET 要求は、送信者がサーバのリソースに関する情報を取得するときに使用します。サーバはこの要求を受信して処理すると、要求情報を受信者に返します。GET 要求は、ブラウザで特定のアドレスに移動することに似ています。Web ページにアクセスすると、ブラウザはバックグラウンドで GET 要求を実行します。ブラウザでは、この移動の結果は表示される新しい HTML ページになります。コールアウトでは、この結果は応答オブジェクトになります。

GET 要求のしくみを説明するため、ブラウザを開いて https://th-apex-http-callout.herokuapp.com/animals の URI に移動します。サービスから応答が JSON 形式で返されるため、奇妙な形式の動物のリストがブラウザに表示されます。GET 応答は、XML 形式の場合もあります。

次に、一般的な HTTP メソッドについて説明します。

表 1.一般的な HTTP メソッド
HTTP メソッド 説明
GET URL で識別されるデータを取得します。
POST リソースを作成したり、サーバにデータを投稿したりします。
DELETE URL で識別されるリソースを削除します。
PUT リクエストボディで送信されるリソースを作成または置換します。

時間があれば、「リソース」セクションで、すべての HTTP メソッドの完全なリストを参照してください。

HTTP メソッドのほかに、各要求で URI (サービスが存在するエンドポイントアドレス) を設定します。たとえば、エンドポイントは http://www.example.com/api/resource となります。「HTTP とコールアウトの基本」単元の例では、エンドポイントは https://th-apex-http-callout.herokuapp.com/animals です。

サーバは要求を処理すると、応答で状況コードを送信します。状況コードは、要求が正常に処理されたかどうか、またはエラーが発生したかどうかを示します。要求が正常に処理されると、サーバは状況コード 200 を送信します。他にも 404 (ファイルが見つかりません) や 500 (内部サーバエラー) などの状況コードを見たことがあるかもしれません。

HTTP メソッドのリストを参照した後で時間がある場合は、「リソース」セクションですべての応答の状況コードのリストを確認してください。寝付けない夜には、これらの 2 つのリソースが役立ちます。

メモ

メモ

エンドポイントや HTTP メソッドに加えて、要求には他のプロパティを設定できます。たとえば、要求に関する詳細 (コンテンツタイプなど) を提供するヘッダーを含めることができます。また、要求には、サービスに送信されるデータ (POST 要求など) を含めることもできます。POST 要求の例は、後の手順に記載されています。POST 要求は、Web ページのボタンをクリックしてフォームデータを送信することに似ています。コールアウトでは、Web ページにデータを手動で入力する代わりに、リクエストボディの一部としてデータを送信します。

サービスからのデータの取得

HTTP の新しい知識を Apex コールアウトで活用してみましょう。この例では、GET 要求を Web サービスに送信して、森に住む動物のリストを取得します。サービスは JSON 形式で応答を送信します。JSON は基本的に文字列であるため、組み込みの JSONParser クラスでオブジェクトに変換します。その後、そのオブジェクトを使用して各動物の名前をデバッグログに書き込むことができます。

この単元の例を実行する前に、「エンドポイントアドレスの承認」セクションの手順を使用して、コールアウトのエンドポイント URL (https://th-apex-http-callout.herokuapp.com) を承認する必要があります。

  1. 設定ギア (設定ギアアイコン) から開発者コンソールを開きます。
  2. 開発者コンソールで、[Debug (デバッグ)] | [Open Execute Anonymous Window (実行匿名ウィンドウを開く)] を選択します。
  3. 既存のコードを削除し、次のスニペットを挿入します。
    Http http = new Http();
    HttpRequest request = new HttpRequest();
    request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');
    request.setMethod('GET');
    HttpResponse response = http.send(request);
    // If the request is successful, parse the JSON response.
    if (response.getStatusCode() == 200) {
        // Deserialize the JSON string into collections of primitive data types.
        Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
        // Cast the values in the 'animals' key as a list
        List<Object> animals = (List<Object>) results.get('animals');
        System.debug('Received the following animals:');
        for (Object animal: animals) {
            System.debug(animal);
        }
    }
  4. [Open Log (ログを開く)] を選択し、[Execute (実行)] をクリックします。
  5. デバッグログが開いたら、[Debug Only (デバッグのみ)] を選択して System.debug ステートメントの出力を表示します。
    動物の名前が表示されます。

この例の JSON は非常に単純で解析も簡単です。より複雑な JSON 構造には、JSON2Apex を使用できます。このツールでは、JSON 構造を解析するために強く型付けされた Apex コードが生成されます。JSON に貼り付けるだけで、ツールによって必要な Apex コードが生成されます。素晴らしいですね!

サービスへのデータの送信

HTTP コールアウトのもう一つの一般的な用途は、サービスにデータを送信することです。たとえば、Justin Bieber の最新アルバムを購入する場合や、お気に入りの動画「Cat in a Shark Costume Chases a Duck While Riding a Roomba」にコメントする場合、ブラウザは POST 要求を実行してデータを送信します。では、Apex でデータを送信する方法を見ていきましょう。

この例では、POST 要求を Web サービスに送信して動物の名前を追加します。新しい名前が JSON 文字列としてリクエストボディに追加されます。要求の Content-Type ヘッダーが設定されているので、サービスは送信データが JSON 形式であることを認識してデータを適切に処理できます。サービスは、状況コードとすべての動物のリスト (追加した動物を含む) を送信して応答します。要求が正常に処理されると、リソースが作成され、状況コード 201 が返されます。201 以外が返されると、応答はデバッグログに送信されます。

  1. 設定ギア (設定ギアアイコン) から開発者コンソールを開きます。
  2. 開発者コンソールで、[Debug (デバッグ)] | [Open Execute Anonymous Window (実行匿名ウィンドウを開く)] を選択します。
  3. 既存のコードを削除し、次のスニペットを挿入します。
    Http http = new Http();
    HttpRequest request = new HttpRequest();
    request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');
    request.setMethod('POST');
    request.setHeader('Content-Type', 'application/json;charset=UTF-8');
    // Set the body as a JSON object
    request.setBody('{"name":"mighty moose"}');
    HttpResponse response = http.send(request);
    // Parse the JSON response
    if (response.getStatusCode() != 201) {
        System.debug('The status code returned was not expected: ' +
            response.getStatusCode() + ' ' + response.getStatus());
    } else {
        System.debug(response.getBody());
    }
  4. [Open Log (ログを開く)] を選択し、[Execute (実行)] をクリックします。
  5. デバッグログが開いたら、[Debug Only (デバッグのみ)] を選択し、System.debug ステートメントの出力を表示します。動物のリストの最後の項目は、"mighty moose" です。

コールアウトのテスト

コールアウトのテストについては、良いニュースと悪いニュースがあります。悪いニュースは、Apex テストメソッドはコールアウトをサポートしていないため、コールアウトを実行するテストは失敗するということです。良いニュースは、ランタイムのテストで「疑似」コールアウトを実行できることです。疑似コールアウトでは、実際に Web サービスをコールせずに、テストで返される応答を指定できます。要するに、「この Web サービスから返される内容はわかっているので、テスト中に Web サービスをコールする代わりにこのデータを返してほしい」とランタイムに伝えます。テストで疑似コールアウトを使用すると、十分なコードカバー率を達成でき、コールアウトによるコード行のスキップを回避できます。

前提条件

テストを作成する前に、「サービスへのデータの送信」単元で匿名で実行した GET 要求と POST 要求の例が含まれるクラスを作成しましょう。これらの例は、要求がメソッド内に配置されて値を返すように若干変更されていますが、基本的に前の例と同じです。

  1. 開発者コンソールで、[File (ファイル)] | [New (新規)] | [Apex Class (Apex クラス)] を選択します。
  2. クラス名として「AnimalsCallouts」と入力し、[OK] をクリックします。
  3. 自動生成されるコードを次のクラス定義で置き換えます。
    public class AnimalsCallouts {
    
        public static HttpResponse makeGetCallout() {
            Http http = new Http();
            HttpRequest request = new HttpRequest();
            request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');
            request.setMethod('GET');
            HttpResponse response = http.send(request);
            // If the request is successful, parse the JSON response.
            if (response.getStatusCode() == 200) {
                // Deserializes the JSON string into collections of primitive data types.
                Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
                // Cast the values in the 'animals' key as a list
                List<Object> animals = (List<Object>) results.get('animals');
                System.debug('Received the following animals:');
                for (Object animal: animals) {
                    System.debug(animal);
                }
            }
            return response;
        }
    
        public static HttpResponse makePostCallout() {
            Http http = new Http();
            HttpRequest request = new HttpRequest();
            request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');
            request.setMethod('POST');
            request.setHeader('Content-Type', 'application/json;charset=UTF-8');
            request.setBody('{"name":"mighty moose"}');
            HttpResponse response = http.send(request);
            // Parse the JSON response
            if (response.getStatusCode() != 201) {
                System.debug('The status code returned was not expected: ' +
                    response.getStatusCode() + ' ' + response.getStatus());
            } else {
                System.debug(response.getBody());
            }
            return response;
        }        
    
    }
  4. CTRL+S キーを押して保存します。

StaticResourceCalloutMock を使用したコールアウトのテスト

コールアウトをテストするには、インターフェースを実装するか、静的リソースを使用して疑似コールアウトを使用します。この例では、まず静的リソースを使用し、後で疑似インターフェースを使用します。静的リソースには、返すレスポンスボディが含まれます。繰り返しになりますが、疑似コールアウトを使用する場合、要求はエンドポイントに送信されません。代わりに、Apex ランタイムが、静的リソースで指定された応答を検索して返すことを認識しています。Test.setMock メソッドは、疑似コールアウトがテストメソッドで使用されていることをランタイムに通知します。実際の疑似コールアウトの動作を見てみましょう。まず、GET 要求で使用する JSON 形式の文字列が含まれる静的リソースを作成します。

  1. 開発者コンソールで、[File (ファイル)] | [New (新規)] | [Static Resource (静的リソース)] を選択します。
  2. 名前として「GetAnimalResource」と入力します。
  3. JSON を使用していますが、MIME タイプに [text/plain] を選択します。
  4. [送信] をクリックします。
  5. リソースの開いたタブで、次のコンテンツを挿入します。すべてが 1 行に収まっていて、改行されていないことを確認します。次に、疑似コールアウトで返される内容を示します。これは森に住む 3 種類の動物の配列です。
    {"animals": ["pesky porcupine", "hungry hippo", "squeaky squirrel"]}
  6. CTRL+S キーを押して保存します。

静的リソースが正常に作成されました。次は、このリソースを使用するコールアウトのテストを追加しましょう。

  1. 開発者コンソールで、[File (ファイル)] | [New (新規)] | [Apex Class (Apex クラス)] を選択します。
  2. クラス名として「AnimalsCalloutsTest」と入力し、[OK] をクリックします。
  3. 自動生成されるコードを次のテストクラス定義で置き換えます。
    @isTest
    private class AnimalsCalloutsTest {
    
        @isTest static  void testGetCallout() {
            // Create the mock response based on a static resource
            StaticResourceCalloutMock mock = new StaticResourceCalloutMock();
            mock.setStaticResource('GetAnimalResource');
            mock.setStatusCode(200);
            mock.setHeader('Content-Type', 'application/json;charset=UTF-8');
            // Associate the callout with a mock response
            Test.setMock(HttpCalloutMock.class, mock);
            // Call method to test
            HttpResponse result = AnimalsCallouts.makeGetCallout();
            // Verify mock response is not null
            System.assertNotEquals(null,result,
                'The callout returned a null response.');
            // Verify status code
            System.assertEquals(200,result.getStatusCode(),
              'The status code is not 200.');
            // Verify content type   
            System.assertEquals('application/json;charset=UTF-8',
              result.getHeader('Content-Type'),
              'The content type value is not expected.');  
            // Verify the array contains 3 items     
            Map<String, Object> results = (Map<String, Object>) 
                JSON.deserializeUntyped(result.getBody());
            List<Object> animals = (List<Object>) results.get('animals');
            System.assertEquals(3, animals.size(),
              'The array should only contain 3 items.');          
        }   
    
    }
  4. CTRL+S キーを押して保存します。
  5. [Test (テスト)] | [Always Run Asynchronously (常に非同期に実行)] を選択します。[Always Run Asynchronously (常に非同期に実行)] が選択されていない場合は、1 つのクラスのみが含まれるテスト実行が同期して実行されます。同期テスト実行の場合にのみ [Tests (テスト)] タブからログを開くことができます。
  6. テストを実行するには、[Test (テスト)] | [New Run (新規実行)] を選択します。
  7. [Test Classes (テストクラス)] リストから、[AnimalsCalloutsTest] を選択します。
  8. [Add Selected (選択された項目を追加)] | [Run (実行)] をクリックします。

[Tests (テスト)] タブのテスト実行 ID の下にテスト結果が表示されます。テスト実行が完了したら、テスト実行を展開して、詳細を表示します。次に、[Overall Code Coverage (全体のコードカバー率)] ペインで [AnimalCallouts] をダブルクリックして、テストでカバーされた行を確認します。

HttpCalloutMock を使用したコールアウトのテスト

POST コールアウトのテストのために、HttpCalloutMock インターフェースの実装が用意されています。このインターフェースでは、respond メソッドで送信される応答を指定できます。テストクラスは、Test.setMock を再度コールしてこの疑似応答を送信するように Apex ランタイムに指示します。第 1 引数で、HttpCalloutMock.class を渡します。第 2 引数では、HttpCalloutMock のインターフェース実装である AnimalsHttpCalloutMock の新しいインスタンスを渡します。この例の AnimalsHttpCalloutMock は、この後に作成します。

Test.setMock(HttpCalloutMock.class, new AnimalsHttpCalloutMock());

次に、HttpCalloutMock インターフェースを実装するクラスを追加して、コールアウトを捕捉します。HTTP コールアウトがテストコンテキストで呼び出された場合、コールアウトは実行されません。代わりに、AnimalsHttpCalloutMockrespond メソッド実装で指定した疑似応答が受信されます。

  1. 開発者コンソールで、[File (ファイル)] | [New (新規)] | [Apex Class (Apex クラス)] を選択します。
  2. クラス名として「AnimalsHttpCalloutMock」と入力し、[OK] をクリックします。
  3. 自動生成されるコードを次のクラス定義で置き換えます。
    @isTest
    global class AnimalsHttpCalloutMock implements HttpCalloutMock {
        // Implement this interface method
        global HTTPResponse respond(HTTPRequest request) {
            // Create a fake response
            HttpResponse response = new HttpResponse();
            response.setHeader('Content-Type', 'application/json');
            response.setBody('{"animals": ["majestic badger", "fluffy bunny", "scary bear", "chicken", "mighty moose"]}');
            response.setStatusCode(200);
            return response; 
        }
    }
  4. CTRL+S キーを押して保存します。

次の例のように、テストクラスで testPostCallout メソッドを作成して疑似コールアウトを設定します。testPostCallout メソッドは Test.setMock をコールしてから AnimalsCallouts クラスの makePostCallout メソッドをコールします。次に、返された応答が、疑似実装の応答メソッドで指定した内容であることを確認します。

  1. テストクラス AnimalsCalloutsTest を変更し、2 番目のテストメソッドを追加します。クラスタブをクリックし、閉じ括弧の前に次のメソッドを追加します。
    @isTest static void testPostCallout() {
        // Set mock callout class 
        Test.setMock(HttpCalloutMock.class, new AnimalsHttpCalloutMock()); 
        // This causes a fake response to be sent
        // from the class that implements HttpCalloutMock. 
        HttpResponse response = AnimalsCallouts.makePostCallout();
        // Verify that the response received contains fake values
        String contentType = response.getHeader('Content-Type');
        System.assert(contentType == 'application/json');
        String actualValue = response.getBody();
        System.debug(response.getBody());
        String expectedValue = '{"animals": ["majestic badger", "fluffy bunny", "scary bear", "chicken", "mighty moose"]}';
        System.assertEquals(actualValue, expectedValue);
        System.assertEquals(200, response.getStatusCode());
    }
  2. CTRL+S キーを押して保存します。
  3. [Test (テスト)] | [New Run (新規実行)] を選択します。
  4. [Test Classes (テストクラス)] リストから、[AnimalsCalloutsTest] を選択します。
  5. [Add Selected (選択された項目を追加)] | [Run (実行)] をクリックします。

    [Tests (テスト)] タブの新しいテスト実行 ID の下にテスト結果が表示されます。テスト実行が完了したら、テスト実行を展開して、両方のテストに関する詳細を表示します。

もうひとこと...

トリガおよび非同期 Apex でのコールアウトの使用と、非同期コールアウトの実行について説明します。

メソッドからコールアウトを実行する場合、メソッドは後続のコード行を実行する前に外部サービスからコールアウト応答が送り返されるまで待機します。または、@future(callout=true) アノテーションが付加された非同期メソッドにコールアウトコードを配置したり、Queueable Apex を使用したりできます。これにより、コールアウトが個別のスレッドで実行され、コール元メソッドの実行がブロックされなくなります。

トリガからコールアウトを実行するときは、応答の待機中にコールアウトによってトリガプロセスがブロックされないようにする必要があります。トリガでコールアウトを実行できるようにするには、コールアウトコードが含まれるメソッドに @future(callout=true) アノテーションを付加して、個別のスレッドで実行する必要があります。