Apex REST コールアウト
学習の目的
このモジュールを完了すると、次のことができるようになります。
- コールアウトを実行して、外部サービスからデータを受信する。
- コールアウトを実行して、外部サービスにデータを送信する。
- 疑似コールアウトを使用して、コールアウトをテストする。
一緒にトレイルを進みましょう
エキスパートの説明を見ながらこのステップを実行したい場合は、次の動画をご覧ください。これは Trailhead Live の「Trail Together」(一緒にトレイル) シリーズの一部です。
(この動画は 7:54 の時点から始まります。戻して手順の最初から見直す場合はご注意ください。)
HTTP とコールアウトの基本
REST コールアウトは、HTTP に基づいています。HTTP についていくつか理解しておくことで、コールアウトのしくみを理解しやすくなります。各コールアウト要求は、HTTP メソッドおよびエンドポイントに関連付けられています。HTTP メソッドは、目的のアクション種別を示します。
最も単純な要求は 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 の新しい知識を Apex コールアウトで活用してみましょう。この例では、GET 要求を Web サービスに送信して、森に住む動物のリストを取得します。サービスは JSON 形式で応答を送信します。JSON は基本的に文字列であるため、組み込みの JSONParser
クラスでオブジェクトに変換します。その後、そのオブジェクトを使用して各動物の名前をデバッグログに書き込むことができます。
この単元の例を実行する前に、「エンドポイントアドレスの承認」セクションの手順を使用して、コールアウトのエンドポイント URL (https://th-apex-http-callout.herokuapp.com) を承認する必要があります。
- [Setup (設定)] () から [Developer Console (開発者コンソール)] を開きます。
- 開発者コンソールで、[Debug (デバッグ)] | [Open Execute Anonymous Window (実行匿名ウィンドウを開く)] を選択します。
- 既存のコードを削除し、次のスニペットを挿入します。
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); } }
-
[Open Log (ログを開く)] を選択し、[Execute (実行)] をクリックします。
- デバッグログが開いたら、[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 以外が返されると、応答はデバッグログに送信されます。
- [Setup (設定)] () から [Developer Console (開発者コンソール)] を開きます。
- 開発者コンソールで、[Debug (デバッグ)] | [Open Execute Anonymous Window (実行匿名ウィンドウを開く)] を選択します。
- 既存のコードを削除し、次のスニペットを挿入します。
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()); }
-
[Open Log (ログを開く)] を選択し、[Execute (実行)] をクリックします。
- デバッグログが開いたら、[Debug Only (デバッグのみ)] を選択し、
System.debug
ステートメントの出力を表示します。動物のリストの最後の項目は、"mighty moose" です。
コールアウトのテスト
コールアウトのテストについては、良いニュースと悪いニュースがあります。悪いニュースは、Apex テストメソッドはコールアウトをサポートしていないため、コールアウトを実行するテストは失敗するということです。良いニュースは、ランタイムのテストで「疑似」コールアウトを実行できることです。疑似コールアウトでは、実際に Web サービスをコールせずに、テストで返される応答を指定できます。要するに、「この Web サービスから返される内容はわかっているので、テスト中に Web サービスをコールする代わりにこのデータを返してほしい」とランタイムに伝えます。テストで疑似コールアウトを使用すると、十分なコードカバー率を達成でき、コールアウトによるコード行のスキップを回避できます。
前提条件
テストを作成する前に、「サービスへのデータの送信」単元で匿名で実行した GET 要求と POST 要求の例が含まれるクラスを作成しましょう。これらの例は、要求がメソッド内に配置されて値を返すように若干変更されていますが、基本的に前の例と同じです。
- 開発者コンソールで、[File (ファイル)] | [New (新規)] | [Apex Class (Apex クラス)] を選択します。
- クラス名に
AnimalsCallouts
と入力し、[OK] をクリックします。
- 自動生成されるコードを次のクラス定義で置き換えます。
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; } }
-
CTRL+S キーを押して保存します。
StaticResourceCalloutMock を使用したコールアウトのテスト
コールアウトをテストするには、インターフェースを実装するか、静的リソースを使用して疑似コールアウトを使用します。この例では、まず静的リソースを使用し、後で疑似インターフェースを使用します。静的リソースには、返すレスポンスボディが含まれます。繰り返しになりますが、疑似コールアウトを使用する場合、要求はエンドポイントに送信されません。代わりに、Apex ランタイムが、静的リソースで指定された応答を検索して返すことを認識しています。Test.setMock
メソッドは、疑似コールアウトがテストメソッドで使用されていることをランタイムに通知します。実際の疑似コールアウトの動作を見てみましょう。まず、GET 要求で使用する JSON 形式の文字列が含まれる静的リソースを作成します。
- 開発者コンソールで、[File (ファイル)] | [New (新規)] | [Static Resource (静的リソース)] を選択します。
- 名前に
GetAnimalResource
と入力します。
- JSON を使用していますが、MIME タイプに [text/plain] を選択します。
-
[Submit (送信)] をクリックします。
- リソースの開いたタブで、次のコンテンツを挿入します。次に、疑似コールアウトで返される内容を示します。これは森に住む 3 種類の動物の配列です。
{"animals":["pesky porcupine", "hungry hippo", "squeaky squirrel"]}
[すべてが 1 行に収まっていて、改行されていないことを確認します。]
-
CTRL+S キーを押して保存します。
静的リソースが正常に作成されました。次は、このリソースを使用するコールアウトのテストを追加しましょう。
- 開発者コンソールで、[File (ファイル)] | [New (新規)] | [Apex Class (Apex クラス)] を選択します。
- クラス名に
AnimalsCalloutsTest
と入力し、[OK] をクリックします。
- 自動生成されるコードを次のテストクラス定義で置き換えます。
@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 Assert.areNotEqual(null,result, 'The callout returned a null response.'); // Verify status code Assert.areEqual(200,result.getStatusCode(), 'The status code is not 200.'); // Verify content type Assert.areEqual('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'); Assert.areEqual(3, animals.size(), 'The array should only contain 3 items.'); } }
-
CTRL+S キーを押して保存します。
-
[Test (テスト)] | [Always Run Asynchronously (常に非同期に実行)] を選択します。
[Always Run Asynchronously (常に非同期に実行)] が選択されていない場合は、1 つのクラスのみが含まれるテスト実行が同期して実行されます。非同期テスト実行の場合にのみ [Tests (テスト)] タブからログを開くことができます。
- テストを実行するには、[Test (テスト)] | [New Run (新規実行)] を選択します。
- [Test Classes (テストクラス)] リストから、[AnimalsCalloutsTest] を選択します。
-
[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 コールアウトがテストコンテキストで呼び出された場合、コールアウトは実行されません。代わりに、AnimalsHttpCalloutMock
の respond
メソッド実装で指定した疑似応答が受信されます。
- 開発者コンソールで、[File (ファイル)] | [New (新規)] | [Apex Class (Apex クラス)] を選択します。
- クラス名に
AnimalsHttpCalloutMock
と入力し、[OK] をクリックします。
- 自動生成されるコードを次のクラス定義で置き換えます。
@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; } }
-
CTRL+S キーを押して保存します。
次の例のように、テストクラスで testPostCallout
メソッドを作成して疑似コールアウトを設定します。testPostCallout
メソッドは Test.setMock
をコールしてから AnimalsCallouts
クラスの makePostCallout
メソッドをコールします。次に、返された応答が、疑似実装の応答
メソッドで指定した内容であることを確認します。
- テストクラス
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'); Assert.isTrue(contentType == 'application/json'); String actualValue = response.getBody(); System.debug(response.getBody()); String expectedValue = '{"animals": ["majestic badger", "fluffy bunny", "scary bear", "chicken", "mighty moose"]}'; Assert.areEqual(expectedValue, actualValue); Assert.areEqual(200, response.getStatusCode()); }
-
CTRL+S キーを押して保存します。
-
[Test (テスト)] | [New Run (新規実行)] を選択します。
- [Test Classes (テストクラス)] リストから、[AnimalsCalloutsTest] を選択します。
-
[Add Selected (選択された項目を追加)] | [Run (実行)] をクリックします。
[Tests (テスト)] タブの新しいテスト実行 ID の下にテスト結果が表示されます。テスト実行が完了したら、テスト実行を展開して、両方のテストに関する詳細を表示します。
もうひとこと...
トリガーおよび非同期 Apex でのコールアウトの使用と、非同期コールアウトの実行について説明します。
メソッドからコールアウトを実行する場合、メソッドは後続のコード行を実行する前に外部サービスからコールアウト応答が送り返されるまで待機します。または、@future(callout=true)
アノテーションが付加された非同期メソッドにコールアウトコードを配置したり、Queueable Apex を使用したりできます。これにより、コールアウトが個別のスレッドで実行され、コール元メソッドの実行がブロックされなくなります。
トリガーからコールアウトを実行するときは、応答の待機中にコールアウトによってトリガープロセスがブロックされないようにする必要があります。トリガーでコールアウトを実行できるようにするには、コールアウトコードが含まれるメソッドに @future(callout=true)
アノテーションを付加して、個別のスレッドで実行する必要があります。
リソース
- MDN Web Docs: HTTP request methods (HTTP 要求メソッド)
- MDN Web Docs: HTTP response status codes (HTTP 応答状況コード)
- Apex 開発者ガイド: Apex を使用したコールアウトの呼び出し
- ウィキペディア: Representational state transfer (REST)
- W3C Note: Simple Object Access Protocol (SOAP) 1.1