salesforce学习笔记(2)- 多次调用外部接口(Multiple Http Callouts)测试类的编写
项目中我们经常遇到,在同一个方法中,多次调用外部api接口,与外部系统进行交互。
实例:
当调用某一目标外部接口时,要先获取调用此接口的Access token(全局唯一接口调用凭据)
请求1:用来获取Access token
请求信息1:
https请求方式 | endpoint |
GET | https://api.testapi.com/bin/?XXXXXX/get_token |
请求参数1:
参数 | 必须 | 说明 |
grant_type | true | 获取access_token填写client_credential |
appid | false | |
secret | true | 唯一凭证密钥(appsecret) |
返回值1:
{"access_token":"ACCESS_TOKEN","expires_in":7200}
请求2:在获取此Access token后,access_token将作为参数,进行下一步的接口调用
请求信息2:
https请求方式 | endpoint |
POST | https://api.excuteapi.com/XXXXXX/excute |
请求参数2:
参数 | 必须 | 说明 |
...... | ...... | ...... |
x-token | true | 上述access_token经过处理后作成的参数 |
返回值2:
......
上述过程落实到代码如下:
1 public with sharing class MultipleCalloutFunction { 2 3 // 取得access_token 4 public static TokenResponse getAccessToken() { 5 HttpRequest req = new HttpRequest(); 6 String body = 'grant_type=' + granttype + '&appid=' + appid + '&secret=' + 'appsecret'; 7 req.setMethod('GET'); 8 req.setEndpoint('https://api.testapi.com/bin/?XXXXXX/get_token'); 9 req.setHeader('Content-Type', 'application/x-www-form-urlencoded'); 10 req.setBody(body); 11 Http http = new Http(); 12 HttpResponse res = http.send(req); 13 if (res.getStatusCode() != 200) { 14 return null; 15 } 16 String resbody = res.getBody(); 17 18 TokenResponse tokenResponse = (TokenResponse)JSON.deserialize(resbody, TokenResponse.class); 19 return tokenResponse; 20 } 21 22 public static void excuteMultipleCallout(List<Obj__c> objList) { 23 // 其他逻辑 24 ...... 25 26 TestRequest reqBody = new TestRequest(); 27 reqBody.XXX = YYY; 28 29 HttpRequest req = new HttpRequest(); 30 req.setMethod('POST'); 31 req.setEndpoint('https://api.excuteapi.com/XXXXXX/excute'); 32 req.setHeader('Content-Type','application/json'); 33 // access_token 34 req.setHeader('x-token', MultipleCalloutFunction.accessToken); 35 req.setBody(JSON.serialize(reqBody)); 36 Http http = new Http(); 37 HttpResponse response = http.send(req); 38 39 // 后续逻辑 40 ...... 41 } 42 43 public class TokenResponse { 44 public Integer code{get;set;} 45 public String message{get;set;} 46 public Boolean success{get;set;} 47 public TokenResponseData data{get;set;} 48 } 49 50 public class TokenResponseData { 51 public String accessToken{get;set;} 52 public Integer expiresIn{get;set;} 53 } 54 }
由于测试类中是不允许直接发送http请求的,因此,为了模拟这种情况,我们选择通过实现HttpCalloutMock接口,来获取实际情况中所需要的返回值,测试类代码如下:
1 @IsTest 2 private class MultipleCalloutFunctionTest { 3 4 @isTest 5 static void testGetAccessToken() { 6 Test.startTest(); 7 Test.setMock(HttpCalloutMock.class, new MockHttpResponseGeneratorTest()); 8 MultipleCalloutFunction.TokenResponse tokenResponse = MultipleCalloutFunction.getAccessToken(); 9 Test.stopTest(); 10 } 11 12 @IsTest 13 static void testExcuteMultipleCallout() { 14 // 创建测试数据 15 List<Obj__c> objList = new List<Obj__c>(); 16 Obj__c obj = new Obj__c(); 17 obj.Field1__c = 'a'; 18 obj.Field2__c = 'b'; 19 objList.add(obj); 20 insert objList; 21 22 Test.startTest(); 23 Test.setMock(HttpCalloutMock.class, new MockHttpResponseGeneratorTest()); 24 MultipleCalloutFunction.excuteMultipleCallout(objList); 25 Test.stopTest(); 26 } 27 28 private class MockHttpResponseGeneratorTest implements HttpCalloutMock { 29 public HTTPResponse respond(HTTPRequest req) { 30 // 根据endpoint来区分返回值 31 if (req.getEndpoint().endsWith('token')) { 32 // 创建一个假的返回值 33 HttpResponse res = new HttpResponse(); 34 res.setHeader('Content-Type', 'application/json'); 35 res.setBody('{"code":0,"data":{"accessToken":"afd454d-dsd84gfs-ds4fs4d5","expiresIn":23000},"message":取得accesstoken成功,"success":true}'); 36 res.setStatusCode(200); 37 return res; 38 } else if (req.getEndpoint().endsWith('excute')) { 39 HTTPResponse res = new HTTPResponse(); 40 // 这里的返回值可以根据真实情况去设定 41 res.setBody('{"code":0,"data":"xxxx","message":xxxxxxxx,"success":true}'); 42 res.setStatusCode(200); 43 return res; 44 } else { 45 System.assert(false, 'unexpected endpoint ' + req.getEndpoint()); 46 return null; 47 } 48 } 49 } 50 51 }
总结:
单个callout或者多个callout的场景,测试类写法与常规测试类写法还是有区别的,把握了HttpCalloutMock的方向且清楚根据endpoint来区分返回值,问题就迎刃而解了。
Copyright © 乔木船长