salesforce 零基礎學習(六十八)http callout test class寫法


此篇可以參考:

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_restful_http_testing_httpcalloutmock.htm

https://developer.salesforce.com/trailhead/force_com_dev_intermediate/apex_integration_services/apex_integration_rest_callouts

在項目中我們經常會用到通過http方式和其他系統交互,在salesforce 零基礎學習(三十三)通過REST方式訪問外部數據以及JAVA通過rest方式訪問salesforce這篇講過http callout方式使用,

簡單callout demo如下:

 1 public class CalloutClass {
 2     
 3     //default out of time
 4     private static final Integer OUT_OF_TIME = 10000;
 5     //default method : get
 6     private static final String DEFAULT_METHOD_GET = 'GET';
 7     
 8     private static final Integer STATUS_CODE_OK = 200;
 9     
10     public static String getDataViaHttp(String endPoint,String param) {
11         return getDataViaHttp(endPoint,DEFAULT_METHOD_GET,param);
12     }
13     
14     public static String getDataViaHttp(String endPoint,String method,String param) {
15         return getDataViaHttp(endPoint,method,param,OUT_OF_TIME);
16     }
17     
18     public static String getDataViaHttp(String endPoint,String method,String param,Integer outOfTime) {
19         HttpRequest req = new HttpRequest();
20         Http h = new Http();
21         req.setMethod(method);
22         req.setHeader('Content-Type', 'application/json');
23         if(param != null) {
24             req.setBody(param);
25         }
26         req.setEndpoint(endPoint);
27         req.setTimeout(outOfTime);
28         HttpResponse res = h.send(req);
29         if(res.getStatusCode() == STATUS_CODE_OK) {
30             return res.getBody();
31         } else {
32             throw new CallOutException('訪問失敗');
33         }
34     }
35     
36     class CallOutException extends Exception {
37         
38     }
39 }

 有的時候我們需要在batch中調用http接口和其他系統交互進行字段更新等操作,如果在batch中需要用到http callout,需要實現Database.AllowsCallouts接口,demo如下:

 1 public with sharing class CalloutBatchClass implements Database.Batchable<sObject>,Database.AllowsCallouts{
 2     public Database.QueryLocator start(Database.BatchableContext BC) {
 3         String fetchSQL = 'fetch sql';
 4         return Database.getQueryLocator(fetchSQL);
 5     }
 6     
 7     public void execute(Database.BatchableContext BC, List<sObject> objList) {
 8         String endPoint = 'site end point';
 9         String responseData = CalloutClass.getDataViaHttp(endPoint,null);
10         for(sObject obj : objList) {
11             //TODO
12         }
13     }
14     
15     public void finish(Database.BatchableContext BC) {
16         
17     }
18 }

 項目中test class是必需的,而且正常要求test class覆蓋率超過75%。test class中不允許http callout,我們可以通過實現HttpCalloutMock接口模擬http請求的返回值。通過重寫respond方法實現

不同的http請求所返回的不同的response狀態和body內容。

 1 @isTest
 2 global class MockHttpResponseGenerator implements HttpCalloutMock {
 3     global String method;
 4     
 5     global String METHOD1_BODY = '{"foo":"bar"}';
 6     
 7     global String METHOD2_BODY = '{"foo":"bar2"}';
 8     
 9     global MockHttpResponseGenerator() {}
10     
11     
12     global MockHttpResponseGenerator(String requestMethod) {
13         method = requestMethod;
14     }
15     
16     // Implement this interface method
17     global HTTPResponse respond(HTTPRequest req) {
18         // Create a fake response
19         HttpResponse res = new HttpResponse();
20         res.setHeader('Content-Type', 'application/json');
21         String body;
22         if(method == 'method1') {
23             body = METHOD1_BODY;
24         } else if(method == 'method2') {
25             body = METHOD2_BODY;
26         } else if(method == 'methodError') {
27             res.setStatusCode(500);
28         }
29         res.setBody('{"foo":"bar"}');
30         if(res.getStatusCode() != null) {
31             res.setStatusCode(200);
32         }
33         return res;
34     }
35 }

簡單的測試CalloutClass的測試類如下:

@isTest
private class CalloutClassTest {
     @isTest static void testSuccessCallout() {
         Test.startTest();
        // Set mock callout class 
        Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator('method1'));
        String endPoint = 'http://api.salesforce.com/foo/bar';
        String result = CalloutClass.getDataViaHttp(endPoint,'test param');
        String expectedValue = '{"foo":"bar"}';
        System.assertEquals(result, expectedValue);
        Test.stopTest();
    }
}

 這只是我們碰到的所謂最理想的情況,有的時候我們往往會碰到這樣一種情況:一個方法里面需要調用到多個http callout。比如需要先進行http callout,將返回值作為參數或者oauth setting內容然后繼續進行callout,這種情況下使用上述的方式便比較難實現,畢竟上述mock形式僅作為一個http callout的response。這個時候我們要變通一下,看看前面的調用是否是必要的--前后幾次調用是否有並列關系,還是僅將前幾次調用作為相關參數為最后一次做准備,此種情況下,可以在類中設置相關的靜態變量來跳過相關的調用;如果前后幾次調用屬於並列關系,需要對每一次的response的內容進行相關處理,這種情況下的test class便需要使用multi mock形式。

一.非並列關系:此種方式可以使用變量方式跳過相關的調用

 1 public with sharing class CalloutClassUseVariable {
 2     public static Boolean skipForTest{get;set;}
 3     public STring getResult(String endPoint1,String endPoint2) {
 4         String result1 = '';
 5         if(skipForTest == null ||skipForTest == false) {
 6             result1 = CalloutClass.getDataViaHttp(endPoint1,'');
 7         }
 8         String result2 = CalloutClass.getDataViaHttp(endPoint2,result1);
 9         return result2;
10     }
11 }

相關test class處理

 1 @isTest
 2 private class CalloutClassUseVariableTest {
 3     static testMethod void testMethod1() {
 4         Test.startTest();
 5         Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator('method1'));
 6         String endPoint = 'http://api.salesforce.com/foo/bar';
 7         CalloutClassUseVariable.skipForTest = true;
 8         String result = CalloutClassUseVariable.getResult('', endPoint);
 9         String expectedValue = '{"foo":"bar"}';
10         System.assertEquals(result, expectedValue);
11         Test.stopTest();
12     }
13 }

二.並列關系:此種方式需要使用MultiStaticResourceCalloutMock方式。

salesforce提供MultiStaticResourceCalloutMock接口實現多個callout的test class模擬response請求,可以將response的body值放在txt文檔中上傳至static resources中,然后test class引用相關靜態資源實現模擬多個response返回。

1 public with sharing class CalloutController {
2     public String result{get;set;}
3     public void getResult(String endPoint1,String endPoint2) {
4         String result1 = CalloutClass.getDataViaHttp(endPoint1,'');
5         String result2 = CalloutClass.getDataViaHttp(endPoint2,'');
6         result = result1 + result2;
7     }
8 }

相關test class處理:

1.將需要的相關response body值上傳至static resource中;

2.test class編寫

 1 @isTest
 2 private class CalloutClassUseMultiStaticResourceTest {
 3     static testMethod void testMethod1() {
 4         MultiStaticResourceCalloutMock mock = new MultiStaticResourceCalloutMock();
 5         String endPoint1 = 'http://api.salesforce.com/foo/bar';
 6         String endPoint2 = 'http://api.salesforce.com/foo/sfdc';
 7         mock.setStaticResource(endPoint1, 'Callout_Method1_TestResponse');
 8         mock.setStaticResource(endPoint2, 'Callout_Method2_TestResponse');
 9         mock.setStatusCode(200);
10         mock.setHeader('Content-Type', 'application/json');        
11         Test.setMock(HttpCalloutMock.class, mock);
12         Test.startTest();
13         CalloutController controller = new CalloutController();
14         controller.getResult(endPoint1,endPoint2);
15         String expectedResult = '{"foo":"bar"}{"foo":"bar2"}';
16         system.assertEquals(expectedResult,controller.result);
17         Test.stopTest();
18     }
19 }

總結:callout test class編寫可以主要看方法中對於callout執行次數以及形式,如果僅是單次請求或者非並列形式,推薦使用httpcalloutMock方式,簡單粗暴,而且自己造數據,不用上傳靜態資源,即使在其他環境下也可以正常跑,如果進行了多次請求,並且請求之間需要有並行操作那就只能使用multi callout 形式,使用此種方式記得在移到其他平台以前將靜態資源上傳。如果篇中有錯誤地方歡迎指正,有問題歡迎留言。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM