Testing multiple related Apex Callouts

Rafik Guennoun
Altius services

--

In complex Salesforce projects, it’s common to have multiple Apex callouts that need to be executed in a specific order, where the result of the first callout is needed to launch the second one, and so on for the rest of the callouts. Testing this process is crucial to ensure that the code is working as expected and to avoid any unexpected behavior.

In this article we are going to see how to do this using the standard apex class ​​MultiStaticResourceCalloutMock

Use case

Here is a use case to illustrate multiple related callouts, the goal of this exemple is to get the customer tax rate. We will follow these steps :

  1. Authenticate to the external system and use the access token in the rest of the steps.
  2. Get customer data and extract the address id.
  3. Get address data and extract the state.
  4. Get the state data and extract the tax rate.
Scenario of the related callouts
Scenario of the related callouts

Endpoints

The base URL of our API will be : https://external-system/
For each endpoint we will give its URL and its response :

Authentication

// URL :  https://external-system/auth

// Response :
{
"access_token": "accessToken123"
}

Get customer data

// URL :  https://external-system/customers/{id} 

// Response :
{
"id": "123456",
"name": "Customer Name",
"email": "Customer@email.com",
"addressId": "789"
}

Get address data

// URL :  https://external-system/addresses/{id} 

// Response :
{
"id": "789",
"street": "123 Main St",
"city": "Anytown",
"state": "CA",
"zipCode": "12345"
}

Get state data

// URL :  https://external-system/states/{name} 

// Response :
{
"name": "CA",
"taxRate": "8.5"
}

Now that we have prepared our external web service, we need to write some code. We will start with the ExternalServiceConnector class

public class ExternalServiceConnector {

// Contains the methods below

}

Helper methods :

// Method that extracts a value from a map using its key :

public static String extractValueWithKey(String response, String key){

Map<String, Object> resBody = (Map<String, Object>) JSON.deserializeUntyped(response);

if (!resBody.containsKey(key)) {
return null;
}

String value = String.valueOf(resBody.get(key));
return value;
}
/* Methods that contain the Rest Callouts */

// [Rest Callout] : Authneticate to the external system
public static String auth(){}

// [Rest Callout] : Get customer data
private static String getCustomerData(String accessToken, String customerID){}

// [Rest Callout] : Get address data
private static String getAddressData(String accessToken, String addressID){}

// [Rest Callout] : Get state data
private static String getStateData(String accessToken, String state){}
// The method that does the full process and return the tax rate

public static decimal getTaxRateOfCustomer(String accessToken, String customerID){

String customerData = getCustomerData(accessToken, customerID);

String addressID = extractValueWithKey(customerData, 'addressId');

String addressData = getAddressData(accessToken, addressID);

String state = extractValueWithKey(addressData, 'state');

String stateData = getStateData(accessToken, state);

String taxRate = extractValueWithKey(customerData, 'taxRate');

return Decimal.valueOf(taxRate);
}

As we said in the introduction, we are going to use the standard apex class​​MultiStaticResourceCalloutMock, from the name of the class, we can see that we’re going to use static resources

We have to create static resources that contain the json responses of each call that we listed above.

Test class

It’s time for the test class, we will write the ExternalServiceConnector that contains the method testGetTaxRate

@isTest
public class ExternalServiceConnectorTest {

@isTest
static void testGetTaxRate(){}

}

Inside the testGetTaxRate function, we’ll get the base URL and create an instance of class MultiStaticResourceCalloutMock

// In a real case the endpoint have to be saved in a custom setting
String endpoint = 'https://external-system';
Srting customerID = '123456';

MultiStaticResourceCalloutMock multimock = new MultiStaticResourceCalloutMock();

After this, we’ll retrieve our static resources and assign them to the multiMock with their respective endpoints :

// Authnetcation 
StaticResource authenticationMock = [SELECT Id, Body FROM StaticResource WHERE Name = 'AuthenticationMock' LIMIT 1];
multimock.setStaticResource(endpoint + '/auth', 'AuthenticationMock');

// Extract access token
String accessToken = ExternalServiceConnector.extractValueWithKey(authenticationMock.Body.toString(), 'access_token');
// Get customer data
StaticResource customerDataMock = [SELECT Id, Body FROM StaticResource WHERE Name = 'CustomerDataMock' LIMIT 1];
multimock.setStaticResource(endpoint + '/customers/' + customerID, 'CustomerDataMock');

// Extract address id
String addressID = ExternalServiceConnector.extractValueWithKey(authenticationMock.Body.toString(), 'addressId');
// Get address data
StaticResource addressDataMock = [SELECT Id, Body FROM StaticResource WHERE Name = 'AddressDataMock' LIMIT 1];
multimock.setStaticResource(endpoint + '/addresses/' + addressID, 'AddressDataMock');

// Extract state name
String stateName = ExternalServiceConnector.extractValueWithKey(authenticationMock.Body.toString(), 'state');
// Get state data
StaticResource stateDataMock = [SELECT Id, Body FROM StaticResource WHERE Name = 'StateDataMock' LIMIT 1];
multimock.setStaticResource(endpoint + '/states/' + stateName, 'StateDataMock');

// Extract tax rate
String taxRateString = ExternalServiceConnector.extractValueWithKey(authenticationMock.Body.toString(), 'taxRate');

Decimal taxRate = Decimal.valueOf(taxRateString);

Then we will add more information to our multiMock like the status code and the header :

// Status code 
multimock.setStatusCode(200);

// Header
multimock.setHeader('Content-Type', 'application/json');
multimock.setHeader('Authorization', 'Bearer ' + accessToken);

Finally, we will start our test and check with an assertion :

Test.setMock(HttpCalloutMock.class, multimock);

Test.startTest();

Decimal taxRateFromMathod = ExternalServiceConnector.getTaxRateOfCustomer(accessToken, customerID);

Test.stopTest();

system.assertEquals(taxRate, taxRateFromMathod, 'Something went wrong');

Here is the full code of the test class :

@isTest
public class ExternalServiceConnectorTest {

@isTest
static void testGetTaxRate(){

// In a real case the endpoint have to be saved in a custom seting
String endpoint = 'https://external-system';
Srting customerID = '123456';

MultiStaticResourceCalloutMock multimock = new MultiStaticResourceCalloutMock();

StaticResource authenticationMock = [SELECT Id, Body FROM StaticResource WHERE Name = 'AuthenticationMock' LIMIT 1];
multimock.setStaticResource(endpoint + '/auth', 'AuthenticationMock');

String accessToken = ExternalServiceConnector.extractValueWithKey(authenticationMock.Body.toString(), 'access_token');

StaticResource customerDataMock = [SELECT Id, Body FROM StaticResource WHERE Name = 'CustomerDataMock' LIMIT 1];
multimock.setStaticResource(endpoint + '/customers/' + customerID, 'CustomerDataMock');

String addressID = ExternalServiceConnector.extractValueWithKey(authenticationMock.Body.toString(), 'addressId');

StaticResource addressDataMock = [SELECT Id, Body FROM StaticResource WHERE Name = 'AddressDataMock' LIMIT 1];
multimock.setStaticResource(endpoint + '/addresses/' + addressID, 'AddressDataMock');

String stateName = ExternalServiceConnector.extractValueWithKey(authenticationMock.Body.toString(), 'state');

StaticResource stateDataMock = [SELECT Id, Body FROM StaticResource WHERE Name = 'StateDataMock' LIMIT 1];
multimock.setStaticResource(endpoint + '/states/' + stateName, 'StateDataMock');

String taxRateString = ExternalServiceConnector.extractValueWithKey(authenticationMock.Body.toString(), 'taxRate');

Decimal taxRate = Decimal.valueOf(taxRateString);

multimock.setStatusCode(200);

multimock.setHeader('Content-Type', 'application/json');
multimock.setHeader('Authorization', 'Bearer ' + accessToken);

System.debug('*** Check multimock : '+ multimock);

Test.setMock(HttpCalloutMock.class, multimock);

Test.startTest();

Decimal taxRateFromMathod = ExternalServiceConnector.getTaxRateOfCustomer(accessToken, customerID);

Test.stopTest();

system.assertEquals(taxRate, taxRateFromMathod, 'Something went wrong');
}
}

Hopefully this tutorial was helpful, let me know if you have any question in the comments.

--

--