项目中我们经常遇到,在同一个方法中,多次调用外部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:
......
上述过程落实到代码如下:
public with sharing class MultipleCalloutFunction {
// 取得access_token
public static TokenResponse getAccessToken() {
HttpRequest req = new HttpRequest();
String body = 'grant_type=' + granttype + '&appid=' + appid + '&secret=' + 'appsecret';
req.setMethod('GET');
req.setEndpoint('https://api.testapi.com/bin/?XXXXXX/get_token');
req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
req.setBody(body);
Http http = new Http();
HttpResponse res = http.send(req);
if (res.getStatusCode() != 200) {
return null;
}
String resbody = res.getBody();
TokenResponse tokenResponse = (TokenResponse)JSON.deserialize(resbody, TokenResponse.class);
return tokenResponse;
}
public static void excuteMultipleCallout(List<Obj__c> objList) {
// 其他逻辑
......
TestRequest reqBody = new TestRequest();
reqBody.XXX = YYY;
HttpRequest req = new HttpRequest();
req.setMethod('POST');
req.setEndpoint('https://api.excuteapi.com/XXXXXX/excute');
req.setHeader('Content-Type','application/json');
// access_token
req.setHeader('x-token', MultipleCalloutFunction.accessToken);
req.setBody(JSON.serialize(reqBody));
Http http = new Http();
HttpResponse response = http.send(req);
// 后续逻辑
......
}
public class TokenResponse {
public Integer code{get;set;}
public String message{get;set;}
public Boolean success{get;set;}
public TokenResponseData data{get;set;}
}
public class TokenResponseData {
public String accessToken{get;set;}
public Integer expiresIn{get;set;}
}
}
由于测试类中是不允许直接发送http请求的,因此,为了模拟这种情况,我们选择通过实现HttpCalloutMock接口,来获取实际情况中所需要的返回值,测试类代码如下:
@IsTest
private class MultipleCalloutFunctionTest {
@isTest
static void testGetAccessToken() {
Test.startTest();
Test.setMock(HttpCalloutMock.class, new MockHttpResponseGeneratorTest());
MultipleCalloutFunction.TokenResponse tokenResponse = MultipleCalloutFunction.getAccessToken();
Test.stopTest();
}
@IsTest
static void testExcuteMultipleCallout() {
// 创建测试数据
List<Obj__c> objList = new List<Obj__c>();
Obj__c obj = new Obj__c();
obj.Field1__c = 'a';
obj.Field2__c = 'b';
objList.add(obj);
insert objList;
Test.startTest();
Test.setMock(HttpCalloutMock.class, new MockHttpResponseGeneratorTest());
MultipleCalloutFunction.excuteMultipleCallout(objList);
Test.stopTest();
}
private class MockHttpResponseGeneratorTest implements HttpCalloutMock {
public HTTPResponse respond(HTTPRequest req) {
// 根据endpoint来区分返回值
if (req.getEndpoint().endsWith('token')) {
// 创建一个假的返回值
HttpResponse res = new HttpResponse();
res.setHeader('Content-Type', 'application/json');
res.setBody('{"code":0,"data":{"accessToken":"afd454d-dsd84gfs-ds4fs4d5","expiresIn":23000},"message":取得accesstoken成功,"success":true}');
res.setStatusCode(200);
return res;
} else if (req.getEndpoint().endsWith('excute')) {
HTTPResponse res = new HTTPResponse();
// 这里的返回值可以根据真实情况去设定
res.setBody('{"code":0,"data":"xxxx","message":xxxxxxxx,"success":true}');
res.setStatusCode(200);
return res;
} else {
System.assert(false, 'unexpected endpoint ' + req.getEndpoint());
return null;
}
}
}
}
总结:
单个callout或者多个callout的场景,测试类写法与常规测试类写法还是有区别的,把握了HttpCalloutMock的方向且清楚根据endpoint来区分返回值,问题就迎刃而解了。
Copyright © 乔木船长
个人主页:乔木船长
欢迎转发点评和指正!