Your logic should be tested completely... for webservice calls there are mock callouts you can make to test out your logic. Salesforce HttpCalloutMock Doc
Pretty much your mock method will have your preloaded response since test cases cannot make callouts, this way your apex logic can run as though it is making a callout.
Your http mock callout will look like such.
global class YourHttpCalloutMockImpl implements HttpCalloutMock {
global HTTPResponse respond(HTTPRequest req) {
// Create a fake response.
// Set response values, and
// return response.
}
}
and your test classes will know what callout to use by providing it with the class
Test.setMock(HttpCalloutMock.class, new YourHttpCalloutMockImpl());
Working example provided by salesforce within the doc is as follows:
HTTP CALLOUT MOCK
@isTest
global class MockHttpResponseGenerator implements HttpCalloutMock {
// Implement this interface method
global HTTPResponse respond(HTTPRequest req) {
// Optionally, only send a mock response for a specific endpoint
// and method.
System.assertEquals('http://api.salesforce.com/foo/bar', req.getEndpoint());
System.assertEquals('GET', req.getMethod());
// Create a fake response
HttpResponse res = new HttpResponse();
res.setHeader('Content-Type', 'application/json');
res.setBody('{"foo":"bar"}');
res.setStatusCode(200);
return res;
}
}
ORIGINAL CALLOUT METHOD
public class CalloutClass {
public static HttpResponse getInfoFromExternalService() {
HttpRequest req = new HttpRequest();
req.setEndpoint('http://api.salesforce.com/foo/bar');
req.setMethod('GET');
Http h = new Http();
HttpResponse res = h.send(req);
return res;
}
}
TEST CASE
@isTest
private class CalloutClassTest {
@isTest static void testCallout() {
// Set mock callout class
Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator());
// Call method to test.
// This causes a fake response to be sent
// from the class that implements HttpCalloutMock.
HttpResponse res = CalloutClass.getInfoFromExternalService();
// Verify response received contains fake values
String contentType = res.getHeader('Content-Type');
System.assert(contentType == 'application/json');
String actualValue = res.getBody();
String expectedValue = '{"foo":"bar"}';
System.assertEquals(actualValue, expectedValue);
System.assertEquals(200, res.getStatusCode());
}
}
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:
- 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.
- 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.
- 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.
Best Answer
Without getting into specifics ( partially because there aren't any, and also because I've only dabbled in this area ) you want to leverage a pattern called Dependency Injection.
Essentially, the idea is to separate your code & calls so that you can pass the result of a call into a helper class. This lets you create mock results in your test class, and test specific parts of a larger system without needing to make a large number of calls. A very simple example is below.
You might have done something close to this - maybe you have a test which has some json in a certain format, which your code is supposed to get from a webservice. Instead of calling the service, you fake a call, and return this explicit json instead. The idea here is exactly the same!
You'll probably need more than one wrapper, and an overhaul of your code in order to make it testable. This is something thats best designed from the start with testing in mind (which is where test-driven development comes from).
Specifically, on the platform, the Stub API is provided to help mock methods. This would be a good place to start looking & designing around.
Heres a better example of setting up a mock (without the stub api):