[SalesForce] Testing a class that has multiple callouts

What is the best practice for writing apex test classes where you have multiple callouts? My Test Class would then be testing the secondCallout method which happens to also call a firstCallout method that has a callout in itself. My question is, what is the appropriate way to be able to run the mock test for both methods?

Main Class

public class myclass {
   public static String firstCallOut() {
        HttpRequest req = new HttpRequest ();
        HttpResponse res = new HttpResponse();
        req.setMethod('POST');
        //code snippets only

        return res.getBody().
   }

   public static void secondCallOut() {
       String resultFromFirstCallout = firstCallOut();

        HttpRequest req = new HttpRequest ();
        req.setMethod('POST');
        req.setBody(resultFromFirstCallout);
        //code snippets only


   }


}

Mock Class

public with sharing class MyMockclass implements HttpCalloutMock {
    
    protected Integer code;
    protected String status;
    protected String body;
    protected Map<String, String> responseHeaders;
    public MyMockclass (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 request) {
        // Create a fake response
        HttpResponse response = new HttpResponse();
        for (String key : this.responseHeaders.keySet()) {
          response.setHeader(key, this.responseHeaders.get(key));
        }
        response.setBody(this.body);
        response.setStatusCode(this.code);
        response.setStatus(this.status);
        return response;


    }

}

Test Class

@isTest(seeAllData=false)
public class myclassTest{
    static testMethod void myTestMethod(){


            Map<String, String> responseHeader = new Map<String, String>();
            responseHeader .put('Content-Type','application/json');

            MyMockclass mock= new MyMockclass (
                200,
                'success',
                'Succesful',
                responseHeader 
            );
            Test.setMock(HttpCalloutMock.class, mock);


            Test.startTest();
            myclass.secondCallOut();
            Test.stopTest();




    }


Best Answer

There are a couple of ways you can test this when you have more than one apex callout. Adopt any one of these approaches based on your preference.

1. Using MultiStaticResourceCalloutMock and Static Resources.

In this approach, you put mock responses in the static resource that is a text file with mock data in JSON format.

An example of this as below and can be found here in docs

@isTest
private class CalloutMultiStaticClassTest {
    @isTest static void testCalloutWithMultipleStaticResources() {
    // Use MultiStaticResourceCalloutMock to
    // specify fake response for a certain endpoint and 
    // include response body in a static resource.    
    MultiStaticResourceCalloutMock multimock = new MultiStaticResourceCalloutMock();
    multimock.setStaticResource(
        'http://example.com/example/test', 'mockResponse'); // mockResponse is a static resource file with mock response
    multimock.setStaticResource(
        'http://example.com/example/sfdc', 'mockResponse2'); // mockResponse2 is a static resource file with mock response
    multimock.setStatusCode(200);
    multimock.setHeader('Content-Type', 'application/json');
    
    // Set the mock callout mode
    Test.setMock(HttpCalloutMock.class, multimock);
    
    // Call the method for the first endpoint
    HTTPResponse res = CalloutMultiStaticClass.getInfoFromExternalService(
        'http://example.com/example/test');
    // Verify response received 
    System.assertEquals('{"hah":"fooled you"}', res.getBody());
    
    // Call the method for the second endpoint
    HTTPResponse res2 = CalloutMultiStaticClass.getInfoFromExternalService(
        'http://example.com/example/sfdc');
    // Verify response received 
    System.assertEquals('{"hah":"fooled you twice"}', res2.getBody());
  }
}

2. Creating a MultiRequest mock class that has a constructor that accepts Map<String, HttpCalloutMock> requests where the map key is the endpoint. The respond method responds based on the endpoint provided.

public class MultiRequestMock implements HttpCalloutMock {
Map<String, HttpCalloutMock> requests;

public MultiRequestMock(Map<String, HttpCalloutMock> requests) {
    this.requests = requests;
}

public HTTPResponse respond(HTTPRequest req) {
    HttpCalloutMock mock = requests.get(req.getEndpoint());
    if (mock != null) {
        return mock.respond(req);
    } else {
        throw new MyCustomException('HTTP callout not supported for test methods');
    }
}

  public void addRequestMock(String url, HttpCalloutMock mock) {
    requests.put(url, mock);
  }
}

check the complete example here

Related Topic