[SalesForce] Mock Unit Test for a REST API callout

After attending DF14 I have a fairly solid understanding of the HttpCalloutMock() when unit testing for REST callouts, but I'm having trouble implementing unit testing for the first time. The documentation I've gone through so far hasn't helped me connect all the dots for my use-case.

[REVISED QUESTION]

I have a trigger that fires after update of a Lead that checks if an Email has been added, and if so then it will execute Pardot.createProspectsByContact(Trigger.new); which makes two @future calls see here:

LeadTriggerHandler.cls

private void isEmailAdded (){
    for(Lead l : (List<Lead>) Trigger.new){
        Lead old = (Lead) Trigger.oldMap.get(l.Id);
        /* If Email was empty */
        if ( old.Email == null && l.Email != null ){
            /* Add Lead Prospect */
            Pardot.createProspectsByLead(Trigger.new);  <-- HERE IS WHEN THE ACTION STARTS
            System.debug('::LEAD SENT TO PARDOT::');
        }
    }
}

Pardot.cls

public static void createProspectsByLead (Lead[] leads){
    for (Lead l :leads) {
        APIRequest(
            'https://pi.pardot.com/api/prospect/version/3/do/create', // API Request
            'email='+l.Email // Required parameter
        );
        System.debug('::PARDOT EMAIL::'+l.Email);
    }
}

Then I have 2 separate test class files, LeadTest.cls & PardotTest.cls, both of these attempt to follow the practices listed here

1) LeadTest.cls (all my current test pass except this one)

static testMethod void addEmail(){
    Lead lead = createLead_basic();
    lead.Email = 'jsmith@smithandco.com';

    Test.startTest(); <-- NOT SURE IF THIS IS NECESSARY...
        update lead; <-- BECAUSE EMAIL WAS BLANK THIS WILL INVOKE Pardot.createProspectsByLead()
    Test.stopTest();
}

Which then leads me to the test class I'm having issues/questions with. Here is an outline of the methods involed

2) PardotTest.cls (I'm understanding that there should by a method for each method within Pardot.cls to account for complete coverage but I don't understand how to do that here)

/* Just as in Pardot.cls, but how do I make the UPDATE LEAD trigger use this function during testing
@isTest public static void createProspectsByLeadTEST (Lead[] leads){
    for (Lead l :leads) {
        APIRequestTEST(
            'https://pi.pardot.com/api/prospect/version/3/do/create',
            'email='+l.Email
        );
    }
}   

[ORIGINAL CONTEXT]

  • The methods that fetch the Global__c (Class.Pardot.getConnectorEmail) return empty since no data exist during the unit test.

    Questions 1: Can I create a separate unit test that populates this data to be used when this test is run? For example: -RESOLVED- (see @Keith C 's answer)

    Question 2: Do I need to put something in the Pardot.cls or PardotTest.cls that implements HttpCalloutMock? I don't understand when writing something like this, how it knows what method it is invoking at the time the test is executed (currently I am naming the test methods orignalMethodNameTest() ).

    Question 3: How do I use the response of the first HttpCalloutMock to pass into the next HttpCalloutMock, or do I even need to since it will be a mock call anyways? (Essentially I just need all the lines to hit, not that the data has to be associated like a real-world example, correct?)

Previous Questions

Best Answer

Below is the sort of test class I would write for this situation. I assume the aim is to test your own code not the 3rd party code so this aims to do the minimum to get the sequence of requests to work.

The core technique where multiple HTTP callouts are involved is to make your mock class support multiple requests by using if/else logic based on the incoming request.

@isTest
private class Test_Lead {

    private class Mock implements HttpCalloutMock {
        public HTTPResponse respond(HTTPRequest req) {
            HTTPResponse res = new HTTPResponse();
            res.setStatusCode(200);
            if (req.getEndpoint().endsWith('apikey') {
                // Return a fake API_Key in the response
                res.setBody(...);
            } else if (req.getEndpoint().endsWith('apirequest') {
                // Mock the response if necessary
                res.setBody(...);
            }
            return res;
        }
    }

    @isTest
    static void addEmail() {

        insertPardotConnectorEmail();

        Lead lead = createLead_basic();
        lead.Email = 'jsmith@smithandco.com';

        // HTTP calls get routed to mock instead of out to the internet
        Test.setMock(HttpCalloutMock.class, new Mock());

        Test.startTest();
        update lead;
        Test.stopTest();
    }

    private static Global__c insertPardotConnectorEmail(){
        //Create test data
        Global__c setting = new Global__c(
                Name = 'Pardot Connector Email',
                Value__c = 'pardot+admin@raprec.com'
        );
        insert setting;
        return setting;
    }

    private static Lead createLead_basic() {
        ...
    }
}

On your questions:

  1. Yes you can move some of the logic out to other classes but that is only worth doing if you have multiple test classes. Usually its simpler to have one test class with multiple test methods.
  2. Doesn't Pardot.cls already have its own test class? I'm assuming your aim is to test your own code and just mock enough environment so that Pardot.cls works and makes the necessary calls when you invoke it through your trigger.
  3. Again, I'm assuming that Pardot.cls will pass on the value and that you are not trying to verify that because the focus of the test is your own code.