Salesforce Developers Blog

Testing Apex Callouts using HttpCalloutMock

Avatar for Sandeep BhanotSandeep Bhanot
Learn how to test Apex HTTP callouts using the new HttpCalloutMock feature.
March 11, 2013
Listen to this article
0:00 / 0:00

The Winter ’13 release added a couple of new features for testing Apex callouts. Specifically, developers can now either

  1. Use a Static Resource to save the test response (e.g. a JSON text file) and then use the  StaticResourceCalloutMock or MultiStaticResourceCalloutMock system classes to test one or more callouts, or
  2. Implement one of two new interfaces: HttpCalloutMock, for HTTP (REST) callouts, or WebServiceMock, for Web Service (SOAP) callouts

Pat wrote an excellent blog post breaking down the first option. Lets review the second option, specifically how to implement the HttpCalloutMock interface to test HTTP callouts.

Testing a single HTTP callout

Lets start with a relatively simple requirement of testing an Apex class that makes a single HTTP (i.e. REST) callout. Here’s the code that we need to test.

public class CalloutAccounts {
    public static List<Account> getAccounts() {
        HttpRequest req = new HttpRequest();
        req.setEndpoint('http://api.example.com/accounts');
        req.setMethod('GET');
        Http h = new Http();
        HttpResponse res = h.send(req);
        String jsonData = res.getBody();
        List<Account> accounts =
            (List<Account>)JSON.deserialize(jsonData, List<Account>.class);
        return accounts;
    }
}

The first step is to create a class that implement the HttpCalloutMock interface.

@isTest
public class SingleRequestMock implements HttpCalloutMock {
		protected Integer code;
		protected String status;
		protected String bodyAsString;
		protected Blob bodyAsBlob;
		protected Map<String, String> responseHeaders;

		public SingleRequestMock(Integer code, String status, String body,
                                         Map<String, String> responseHeaders) {
			this.code = code;
			this.status = status;
			this.bodyAsString = body;
			this.bodyAsBlob = null;
			this.responseHeaders = responseHeaders;
		}

		public SingleRequestMock(Integer code, String status, Blob body,
                                         Map<String, String> responseHeaders) {
			this.code = code;
			this.status = status;
			this.bodyAsBlob = body;
			this.bodyAsString = null;
			this.responseHeaders = responseHeaders;
		}

	    public HTTPResponse respond(HTTPRequest req) {
	        HttpResponse resp = new HttpResponse();
			resp.setStatusCode(code);
			resp.setStatus(status);
			if (bodyAsBlob != null) {
				resp.setBodyAsBlob(bodyAsBlob);
			} else {
				resp.setBody(bodyAsString);
			}

			if (responseHeaders != null) {
			     for (String key : responseHeaders.keySet()) {
				resp.setHeader(key, responseHeaders.get(key));
			     }
			}
			return resp;
	    }
}

Note: While it is not required to mark your HttpCalloutMock implementation class as @isTest, it is generally a best practice to do so in order to exclude the class from your organization’s code size limit of 3 MB.

You need to implement the ‘respond’ method of the HttpCalloutMock interface in which you return a fake HttpResponse. As you can see, we’ve created a utility testing class that can be used to test any scenario that involves a single HTTP callout, and not just the CalloutAccounts class shown earlier. This utility class can be used to test both binary and String responses (depending on which constructor you use to instantiate the class) and also lets you specify the HTTP code and status for the fake response. You can optionally also specify HTTP headers to be included in the fake response. Next, lets see how we use this HttpCalloutMock implementation to test our CalloutAccounts class.

@isTest
public class CalloutAccountsTest{
	public static testmethod void testAccountCallout() {
        SingleRequestMock fakeResponse = new SingleRequestMock(200,
                                                 'Complete',
                                                 '[{"Name": "sForceTest1"}]',
                                                 null);
		Test.setMock(HttpCalloutMock.class, fakeResponse);
        CalloutAccounts.getAccounts();
        System.assertEquals(/*check for expected results here...*/);
    }
}

Line 8 shows how we use the Test.setMock system method to tell the platform which HttpCalloutMock implementation to use during the test. After that line, if an HTTP callout is invoked in test context (e.g. the callout code in our CalloutAccounts.getAccounts method), the callout is not made and you receive the mock response specified in the ‘respond’ method implementation. 

Testing multiple HTTP callouts

Lets make things a little more complicated now. Say that your code makes multiple HTTP callouts in a single transaction. Here’s a simple example from Pat’s blog post.
public class ProcessAccountsContacts {
    public static String getJSON(String url) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint(url);
        req.setMethod('GET');
        Http h = new Http();
        HttpResponse res = h.send(req);
        return res.getBody();
    }

    public static Integer processAccountsContacts() {
        String jsonData = getJSON('http://api.example.com/accounts');
        List accounts =
            (List)JSON.deserialize(jsonData, List.class);

        jsonData = getJSON('http://api.example.com/contacts');
        List contacts =
            (List)JSON.deserialize(jsonData, List.class);

        // 'Processing'
        Integer result = accounts.size() + contacts.size();
        return result;
    }
}
Here, we’re making two callouts, one to retrieve the Account data and another to retrieve the Contact data. Using the SingleRequestMock class that we had created above doesn’t work since that class returns the same fake HttpResponse for every callout. One option is to use Static Resources and the MultiStaticResourceCalloutMock class shown in Pat’s blog to return different fake responses based on the HTTP endpoint being invoked. However, that approach cannot handle cases where you need to test for dynamic response data with dependencies on query parameters and/or the request body. Also, what about cases where your code expects a binary response from one callout (e.g. a callout to the Google Map API) and a text/JSON response from another callout. Even though our simple code sample above does not meet either of those two criteria, lets see how we can use HttpCalloutMock to test it.
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);
    }
}
Like our SingleRequestMock utility, the class above also implements the HttpCalloutMock interface. However, unlike SingleRequestMock, this class is simply a wrapper that maintains a Map of HTTP endpoints –> their dummy HttpCalloutMock implementations. Lets see how we can use this utility class to test our multiple callout code.
@isTest
public class MultipleCalloutsTest{
    public static testmethod void testAcctsAndContactsCallout() {
        SingleRequestMock fakeAccountResp = new SingleRequestMock(200,
                                                         'Complete',
                                                         '[{"Name": "sForceTest1"}]',
                                                         null);

        SingleRequestMock fakeContactsResp = new SingleRequestMock(200,
                                                  'Complete',
                                                  '[{"LastName": "Test Last Name"}]',
                                                   null);

        Map<String, HttpCalloutMock> endpoint2TestResp =
                                   new Map<String,HttpCalloutMock>();
        endpoint2TestResp.put('http://api.example.com/accounts',fakeAccountResp);
        endpoint2TestResp.put('http://api.example.com/contacts',fakeContactsResp);

        HttpCalloutMock multiCalloutMock =
            	                   new MultiRequestMock(endpoint2TestResp);

        Test.setMock(HttpCalloutMock.class, multiCalloutMock);
        ProcessAccountsContacts.processAccountsContacts();
        System.assertEquals(/*check for expected results here...*/);
    }
}
In the test method above, we first construct the fake test responses for our two callouts on lines 4-12 using the SingleRequestMock utility class. We then create a Map of the two HTTP endpoints to their respective fake responses and instantiate a MultiRequestMock object out of that. Finally, we use Test.setMock to tell the platform to use the MultiRequestMock object for every HTTP callout made in a test context from that point onward. When we execute our processAccountsContacts code, the ‘respond’ implementation in the MultiRequestMock class will return the appropriate fake response depending on the endpoint being invoked.

Get the latest Salesforce Developer blog posts and podcast episodes via Slack or RSS.

Add to Slack Subscribe to RSS

More Blog Posts

A Deep Dive into Winter '24 Apex Enhancements

A Deep Dive into Winter '24 Apex Enhancements

The Winter '24 release of the Salesforce Platform has brought significant improvements for Apex developers.November 14, 2023

Using Prompt Builder, Flows, and Apex to Summarize and Classify Cases Faster

Using Prompt Builder, Flows, and Apex to Summarize and Classify Cases Faster

With the power of large language models and Prompt Builder, you can classify and summarize standard or custom Salesforce objects easily.June 11, 2024

How to Query Data Cloud from Any Salesforce Org with Apex

How to Query Data Cloud from Any Salesforce Org with Apex

By using next-gen credentials in combination with Apex, you can realize the most complex business cases regarding querying data in a highly flexible and secure way.September 10, 2024