【Apex】コールアウトのテストコードを書くのが意外と簡単だった

## はじめに

Apexから外部システムにリクエストを送ることを「コールアウト」といいます。

結構前に実装したことはあるのですが、
テストコードは書いたことがなかったので、調べてみました。

## 結論

- HttpCalloutMock インターフェースを実装したクラスを作る
- 疑似レスポンスを返すメソッドを実装する


## 詳細

### HttpCalloutMock インターフェースを実装したクラスを作る

- 実装クラスは```global```か```public```
- クラスには```@isTest```をつけよう

サンプルコード）

```
@isTest
global class YourHttpCalloutMockImpl implements HttpCalloutMock {
    global HTTPResponse respond(HTTPRequest req) {
    }
}
```

### 疑似レスポンスを返すメソッド(respond)を実装する

サンプルコード）

```
@isTest
global class MockHttpResponseGenerator implements HttpCalloutMock {

    global HTTPResponse respond(HTTPRequest req) {
        HttpResponse res = new HttpResponse();
        res.setHeader('Content-Type', 'application/json');
        res.setBody('{"example":"test"}');
        res.setStatusCode(200);
        return res;
    }
}
```

### テストコードでTest.setMock()を使いレスポンスを設定

テストコード実行前に以下を書きます。
```Test.setMock(HttpCalloutMock.class, new HttpCalloutMockインターフェースの実装クラス());```

サンプルコード）

```
@isTest
private class CalloutClassTest {
     @isTest static void testCallout() {
        Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator());

        HttpResponse res = CalloutClass.getInfoFromExternalService();
        
        String contentType = res.getHeader('Content-Type');
        System.assert(contentType == 'application/json');

        String actualValue = res.getBody();
        String expectedValue = '{"example":"test"}';
        System.assertEquals(actualValue, expectedValue);
        System.assertEquals(200, res.getStatusCode());
    }
}
```

## その他

- [静的リソース](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_classes_restful_http_testing_static.htm)を使って疑似レスポンスを設定する方法もある
- コールアウトの前にDMLを発行したい場合は、コールアウト部分をTest.startTest()とTest.stopTest()で囲う
サンプルコード）
```
@isTest
private class CalloutClassTest {
     @isTest static void testCallout() {
        Account testAcct = new Account('Test Account');
        insert testAcct;

        Test.startTest();

        Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator());
        
        HttpResponse res = CalloutClass.getInfoFromExternalService();

        String contentType = res.getHeader('Content-Type');
        System.assert(contentType == 'application/json');

        String actualValue = res.getBody();
        String expectedValue = '{"example":"test"}';
        System.assertEquals(actualValue, expectedValue);
        System.assertEquals(200, res.getStatusCode());

        Test.stopTest();
    }
}
```

## 参考文献
- [HTTP コールアウトのテスト | Apex 開発者ガイド | Salesforce Developers](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_classes_restful_http_testing.htm)
- [HttpCalloutMock インターフェースの実装による HTTP コールアウトのテスト | Apex 開発者ガイド | Salesforce Developers](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_classes_restful_http_testing_httpcalloutmock.htm)
- [静的リソースを使用した HTTP コールアウトのテスト | Apex 開発者ガイド | Salesforce Developers](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_classes_restful_http_testing_static.htm)
- [DML 操作と擬似コールアウトの実行 | Apex 開発者ガイド | Salesforce Developers](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_classes_restful_http_testing_dml.htm)