Testing HttpCallouts in Salesforce

Today was the October meeting of the RDU SFDC (developers) user group. Amber talked about permission sets (Especially useful to ISVs!) and our local salesforce developer evangelist showed off workbench. I had the honor of presenting a bit on writing and testing restful web callouts from Salesforce.com.

Web callouts are extremely useful for a number of reasons. Basically they allow you to interact with third party data sources from within the context of a salesforce trigger or class. This allows, for instance, an incoming web-to-lead form’s information to be validated and to set into motion a number of other time-consuming processes that ultimately allows our sales reps to have better data in less time to help close the deal. Attached to this post you’ll find and below you’ll find the actual code of a Salesforce Apex class, and test class illustrating how to write a testable web callout.

TL;DR

Technically, there are only five lines of code needed to make an HTTP callout from salesforce. The only needed lines are object creation for the http obj, the request obj, and the response obj; and the actual execution of the request. However using just these five lines results in code that is unable to be unit tested!
Instead, the five required lines of a web callout must be broken into a series of small, atomic methods. These methods, rather than the actual callout itself, are what must be tested.

The code:

public with sharing class DemoWebCallout {
    public class applicationException extends Exception {}
    Public static HttpRequest buildRequest(Lead l){
        HttpRequest request = new HttpRequest();
        String url = 'http://www.pinguinshow.com/';
        request.setEndpoint(url);
        request.setMethod('GET');
        return request;
    }

    public static HttpResponse makeRequest(Http h, HttpRequest request){
        HttpResponse response = h.send(request);
        return response;
    }  

    public void handleResponse(HttpResponse response){
        // For the purpose of this contrived example, we'll not do anything with the response.
        // If this were not a contrived example, you might see references to:
        // handling xml and other pointy markups languages
        // handling JSON -- if you're on Winter 12. More on that later.
    }

    @future(callout=true)
    Public static void makeWebCallout(Id id) {
        if(id == null) return;
        Lead[] l = [Select l.id From Lead l where l.id = :id limit 1];
        if(!l.isEmpty() || test.isRunningTest()) {
        Http h = new Http(); //Create a new http object
        try {
            HttpRequest request = buildRequest(l[0]); // during a test with a bad id, this will throw an exception
            HttpResponse response = makeRequest(h, request);
        } catch(Exception e){
            System.debug('Failed to execute callout! NO DONUT FOR YOU!! ' + l);
        }
      }     
    }
}

And the tests look like:

@isTest
private class TestDemoWebCallout {

    static testMethod void testRestfulCallout() {
        Lead l = new Lead();
        l.Status = 'Open';
        l.LastName = 'Owen';
        l.FirstName = 'Bob';
        l.Street = '1234 Sesame Street';
        l.City = 'Houston';
        l.Company = 'Test McTesterson and Co.';
        l.State = 'Tx';
        l.PostalCode = '27384';

        insert l;   

        Test.startTest();
        DemoWebCallout AwesomeDemo = new DemoWebCallout();
        HttpRequest testRequest = DemoWebCallout.buildRequest(l);
        System.assertEquals('GET', testRequest.getMethod());
        System.assertEquals('http://www.pinguinshow.com/', testRequest.getEndpoint());
        // working call
        DemoWebCallout.makeWebCallout(l.id);
    //failing call
    DemoWebCallout.makeWebCallout('00QC000000qnwbGMAQ'); //this id should not exist.
    Test.stopTest();
}

Leave a Reply

Your email address will not be published. Required fields are marked *