0

I am struggling to do a test class, mainly building a proper assert method based on the following @RestResource (at the end). This class have 3 different API calls (First API response an XML the other 2 JSON), and some logic in between them that extracts some information of the result and use this to make the next API.

I am aware that I can build a mock and say in the test class run it with this mock response, perhaps 2 more test methods for the other 2 options.

The problem that I am facing when doing the test class is that I can do the mock for the first method but because I need a different sample text for the logic that happens after the second call out my test will fail as it will use the mock information that I passed originally.

In terms of coverage the way I created the class it should be OK as I can independently test the 3 methods (api_1,api_2, api_3), but in terms of having a proper assert at the end of my main HttpPost I will not be able to do it.

Sample class:

@RestResource(urlMapping='/v1/webhook/*')
global without sharing class CustomeWebhook {

    public class SP_Response { 
        public String message;
        public String errorCode;
        public Boolean error = false;
        public Boolean isSandbox = HelperClass.runningInASandbox();
        public Id opportunity_ID;
        public Id contact_ID;
    }
    
    @HttpPost
    global static void create() {
        List<SP_Response> sp_lst = new List<SP_Response>();
        SP_Response sp = new SP_Response();

        RestRequest req = RestContext.request;
        RestResponse res = RestContext.response;
        res.addHeader('Content-Type', 'application/json');
        res.statusCode = 200;
        sp.message = 'waiting';
        
        Object untypedJson;
        String json_txt = req.requestBody.toString();
        try {
            untypedJson = JSON.deserializeUntyped(json_txt);
        } catch (Exception ex) {
            sp.message = 'Invalid schema provided';
            sp.errorCode = 'Invalid_Info';
            sp.error = true;
            res.statusCode = 500;
            sp_lst.add(sp);
            res.responseBody = Blob.valueOf(JSON.serialize(sp_lst));
            return;
        }
        //SOME LOGIC ....


        //STEP 1. API TO EXTERNAL SYSTEM 1
        String api_1 = CustomeWebhook.api_1();
        //SOME LOGIC ....
        //STEP 2. API TO EXTERNAL SYSTEM 2
        String api_2 = CustomeWebhook.api_2();
        //SOME LOGIC ....
        //STEP 3. API TO EXTERNAL SYSTEM 3
        String api_3 = CustomeWebhook.api_3();
        //SOME LOGIC ....

        sp.opportunity_Id = myOpp.Id;
        sp.contact_Id = myCon.Id;
        sp_lst.add(sp);
        res.responseBody = Blob.valueOf(JSON.serialize(sp_lst));
    }

    public static String api_1(String a_val) {
        String api_res;
        //DO API (XML RESPONSE) + LOGIC
        return api_res;
    }

    public static String api_2(String a_val) {
        String api_res;
        //DO API (JSON RESPONSE) + LOGIC
        return api_res;    
    }

    public static String api_3(String a_val) {
        String api_res;
        //DO API (JSON RESPONSE) + LOGIC
        return api_res;
    }
    
}

I thought in doing this in the actual code but my gut tells me is not good.

String api_1;
if(!Test.isRunningTest()){
    api_1 = '...... SAMPLE RES FOR API 1 ';
} else {
    api_1 = CustomeWebhook.api_1();
}    
String api_2;
if(!Test.isRunningTest()){
    api_2 = '...... SAMPLE RES FOR API 1 ';
} else {
    api_2 = CustomeWebhook.api_2();
}
String api_3;
if(!Test.isRunningTest()){
    api_3 = '...... SAMPLE RES FOR API 1 ';
} else {
    api_3 = CustomeWebhook.api_3();
}   

I am wondering for best practices in this kind of scenarios.


My Mock test class looks like this:

@isTest
public class Test_HttpCalloutMock implements HttpCalloutMock {

    protected Integer code;
    protected String status;
    protected String body;
    protected Map<String, String> responseHeaders;

    public Test_HttpCalloutMock(Integer code, String status, String body, Map<String, String> responseHeaders) {
        this.code = code;
        this.status = status;
        this.body = body;
        this.responseHeaders = responseHeaders;
    }

    public HTTPResponse respond(HTTPRequest req) {

        HttpResponse res = new HttpResponse();
        for (String key : this.responseHeaders.keySet()) {
            res.setHeader(key, this.responseHeaders.get(key));
        }
        res.setBody(this.body);
        res.setStatusCode(this.code);
        res.setStatus(this.status);
        return res;
    }

}

and I invoke it like this

Map<String, String> responseHeaders = new Map<String, String>();
responseHeaders.put('Content-Type', 'text/xml');
Test.setMock(HttpCalloutMock.class, new Test_HttpCalloutMock(200,
                            'OK',
                            'OK: Loan Date~Loan Amount~Link|17/03/2009~1118.5~https://www.min-it.net/contract.asp?id=xxxxxxxx|16/07/2009~800~https://www.min-it.net/contract.asp?id=yyyyyy',
                            responseHeaders));
2
  • Show us how you set up your mock class, the code under test is not enough sadly
    – zaitsman
    May 25, 2022 at 23:44
  • @zaitsman I have edited the question and added. However, I dont see how the set up of the mock is relevant in this case, as a test method will be link to max 1 mock, but I added in case I am missing something
    – manza
    May 25, 2022 at 23:47

2 Answers 2

0

So try something like this (note, this is me coding in the browser so you might need to adjust it).

public class MockHttpResponse {
   public Integer code { get; set; }
   public String status { get; set; }
   public String body { get; set; }
   public Map<String, String> responseHeaders { get; set; }
}

@isTest
public class Test_HttpCalloutMock implements HttpCalloutMock {
    protected List<MockHttpResponse> responses;
    protected Integer counter;

    public Test_HttpCalloutMock(List<MockHttpResponse> responses) {
        this.responses = responses;
        this.counter = 0;
    }

    public HTTPResponse respond(HTTPRequest req) {
        
        HttpResponse res = new HttpResponse();
        MockHttpResponse mockResponse = this.responses.get(this.counter);
        for (String key : mockResponse.responseHeaders.keySet()) {
            res.setHeader(key, mockResponse.responseHeaders.get(key));
        }
        res.setBody(mockResponse.body);
        res.setStatusCode(mockResponse.code);
        res.setStatus(mockResponse.status);
        this.counter = this.counter + 1;
        return res;
    }
}

And to use


MockHttpResponse response1 = new MockHttpResponse();
response1.responseHeaders = new Map<String, String>();
response1.responseHeaders.put('Content-Type', 'text/xml');
response1.code = 200;
response1.status = 'OK';
response1.body = 'OK: Loan Date~Loan Amount~Link|17/03/2009~1118.5~https://www.min-it.net/contract.asp?id=xxxxxxxx|16/07/2009~800~https://www.min-it.net/contract.asp?id=yyyyyy;';

// now repeat for response2, response3 etc.

Test.setMock(HttpCalloutMock.class, new Test_HttpCalloutMock(new List<MockHttpResponse> { response1, response2, response3 }));
-1

we can acheive this using map of callouts. Below article clearly explained the way how to do that https://developer.salesforce.com/blogs/developer-relations/2013/03/testing-apex-callouts-using-httpcalloutmock

2
  • 1
    It would be great, if you could summarize the idea of this article here. Articles - even articles by Salesforce - come and go, and StackExchange is meant for eternity :-) Dec 1, 2023 at 13:34
  • 1
    I've downvoted because this has been left as just a link. While the article is fine, this answer will be useless when (not if, when) Salesforce decides to break their link format again.
    – Derek F
    Dec 14, 2023 at 16:11

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .