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.
tagged , , Bookmark the permalink. Trackbacks are closed, but you can post a comment.
  • http://www.tgerm.com/ Abhinav Gupta

    Good post Sandeep about a usecase that would let developers write better test cases for web services i.e. not just testing with one mock implementation. Testing with lot of data combinations is always recommended and this approach will surely simplify.

    I think this could be taken even further by support of equals and hashcode now in Apex collections API, specially MAPS. One can then encapsulate data about various HTTP methods and variable parts about request like headers, params and use them all to keep Key:Value mapping among them, similar to what is done here via URLs.

    This charged me for a blog post, might do a follow up post based on this post soon :)

    • Anonymous

      That does sound like an awesome blog post Abhinav! Post the link to your blog here once its posted.

      • http://www.tgerm.com/ Abhinav Gupta

        Sure

  • Mauricio Ardila

    Just remember to put your DMLs before start test, or you will get an exception.

  • Anonymous

    Hi Sandeep,

    Can you please post the test class for the Google Maps HttpCallouts to get the Latitude and Longitude.

  • Jose Angarita

    Hi Sandeep,

    We have created a class named WebServiceMockImpl that implements WebServiceMock but we don’t know what to put in the response element for it to work. How does one know this?