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.
This answer is not intended to teach you everything about writing unit tests, nor to specifically answer every question, but to provide a quick summary and links to the resources that will help you move forward and develop more specific questions that SFSE can assist with.
Overview
Unit (and integration) testing is a big topic, but it starts with a small set of principles. If you've never written a unit test before, we strongly encourage you to complete the Unit Testing on the Lightning Platform Trailhead module and read at least the Month of Testing series. These materials and others are linked under Resources, below.
Fundamentally, testing comprises three steps, all of which take place within the context of a unit test:
- Creating test data as input for your code, which is designed to ensure that a specific logical path is executed. This can take the form of values in memory or of creating and inserting sObjects.
- Executing that code, meaning that that specific code path runs within a method annotated with the
@isTest
annotation.
- Writing assertions to demonstrate that the results of the code are correct and as expected for the given inputs.
Then, all code that is executed under step (2) is counted as covered under Salesforce's code coverage metrics. Code coverage is a side effect of high quality unit tests. Salesforce uses code coverage as a proxy to measure the presence of unit tests in your deployments.
Unit testing principles are quite general, and most Apex code is not special in the sense of requiring unique approaches to create a successful test. Techniques for implementing tests that perform all three steps are taught in the resources we include below.
Test Isolation
On Salesforce, all unit tests are executed in an isolated context. In this context, your code cannot see data in your organization, including ordinary records as well as Custom Settings. All data must be created via the unit test or @testSetup
method.
Metadata records, including Users and Custom Metadata, are visible in test context.
An older annotation, seeAllData=true
, allows tests to see all data in the Salesforce org. Use of this annotation is strongly discouraged for new unit tests, and is considered a very bad practice. It's important instead to follow the first step above, by designing test data as input for your code. This practice makes unit tests self-contained and repeatable, and insulates them against fragility stemming from data changes.
Smoke Tests (Tests without Assertions)
Unit tests that don't contain assertions are often called smoke tests. These tests have very limited value, because they show nothing other than that your code does not crash under a specific set of circumstances. They don't prove the code works or does what it's intended to do.
Resources
Trailhead
Apex Developer Guide
- Testing Apex
- The
Test
class reference, which includes a variety of testing-related utility methods, including Test.stopTest()
and Test.startTest()
, as well as methods for setting static SOSL results, controlling audit fields, working with mocks and stubs, and other tools.
Blogs and Articles
- Month of Testing series from the Salesforce Developers blog.
Dreamforce Video Content
Third-Party Testing Frameworks (Advanced Topics)
- ApexMocks, an open-source mocking framework for Apex.
- Force-DI, a framework for pervasive dependency injection.
Best Answer
As part of the isolation of the test context, Salesforce does not allow your code to make REST or SOAP callouts during test execution. This includes all code that's executed in test context, even if it is executed indirectly by the code you're explicitly testing, and it includes callouts to Salesforce itself.
To test code that makes callouts, you must develop a Mock class, which mocks the remote server during the test and constructs an appropriate response that's returned to your code. Mock classes may generate a response in code (for example, by constructing objects, serializing them, and returning the resulting JSON), or by returning a result stored in a Static Resource.
Salesforce makes available two Mock interfaces (
HttpCalloutMock
, for REST calls, andWebServiceMock
, for SOAP calls), as well as theStaticResourceCalloutMock
andMultiStaticResourceCalloutMock
implementations. You can use these built-in implementations to test your callouts by providing result data in a Static Resource, which the mock will return to your code in test context. Keep in mind that you must callTest.setMock()
to configure your mock in the test context prior to invoking the code that makes a callout.In many cases, you'll need to use multiple Mock classes, returning different response bodies or status codes to exercise different logic paths in your code. Each unit test would supply an appropriate Mock for the code path whose behavior it's intended to test.
Mock classes are not required to test inbound REST and web service classes. Instead, the classes may be called directly (with appropriate inputs), in a way similar to testing other Apex code.
Resources
To learn how to develop Mock classes, complete these Trailhead modules:
For more in-depth information, explore these sections in the Apex Developer Guide:
HttpCalloutMock
InterfaceWebServiceMock
InterfaceStaticResourceCalloutMock
ClassMultiStaticResourceCalloutMock
Class