In the previous article, you have learned how to use the Framework to set up Callout Resources with configurable and reusable parameters, and also to route every callout to an alternative endpoint which can be another resources server or Salesforce itself. You have got a glimp of what we call Mocking! We will deep dive into the concept of Mocking and its purposes and we will also speak about cool features around logging and tracking.
Prerequisites
Before starting, I will recommend you to read my previous article that has introduced the Callout Framework:
https://ssdevl.com/2021/03/26/art-014-callout-framework-part-1/
Core Framework Settings
The Core Setting CustomMetadataType is the one place to set all the Framework behaviors, you’ll find one for default and one dedicated to Test Context only. It’s important to keep both unchanged as it will be used by the Framework in default mode. If you wish to have dedicated setting, I will show you how to do it later on.

The Default Core Settings is the default base setting used by the Framework when there isn’t any other setting configured for a dedicated Sandbox or Production org, it’s a kind of a fallback setting to ensure the Framework will work in any case.

Setting | Value | Description |
---|---|---|
Label | Default Core Settings | Name of the setting |
Core Setting Name | DefaultCoreSettings | The developername name used in your APEX code |
User Info Session Cache | local.UserInfoCache | Name of the platform cache used to store User Infos |
SObject Info Session Cache | local.UserInfoCache | Name of the platform cache used to store SObject Infos |
is Active? | Checked | If true then the setting will used in current org |
is Default? | Checked | This setting will be used as fallback if not found for current org |
Org Id | Default | This is set to ‘Default’ as it is not org dependent, set the orgId to make it org dependent for any other setting |
With Schema Describer | Checked | Enforce Object and field level security with Schema methods |
With Security Enforced | Checked | Enforce Object and field level security with SOQL keyword |
With StripInaccessible | Checked | Enforce Object and field level security with SObjectAccessDecision methods |
Core Trigger Manager Class | APT001_TriggerEventManager | Name of the core trigger event handler class |
Recursion Level | Number of recursion allowed in trigger execution | |
Logging Enabled | Checked | Enable System debug to display info when using Logging Framework |
Enable Logging Permission | EN_LOGGING | Name of the custom permission to enable logging per user |
Logging Event Enabled | Unchecked | Enable sending Custom Platform Event to track logging |
Enable Logging Event Permission | EN_LOGGING_EVENT | Name of the custom permission to enable logging event per user |
Callout Tracking Enabled | Unchecked | Enable sending Custom Platform Event to track callout |
Enable Callout Tracking Permission | EN_CALLOUT_TRACKING | Name of the custom permission to enable callout tracking per user |
Each sandbox or production org can have it own setting by cloning the default one and by setting the “Org Id” properly (must be unique). The “is Default?” must be unchecked also. In this way, you can deploy the setting between orgs without struggling with overwrite issues. In the default setting, all platform event features are deactivated by default to avoid any consumption of resources, it can be activated globally or per user for a period of time to investigate on issues for example or to maintain a log of activities.
With this version of the Framework, you also have now the possibility to control object and field level security with the pattern that suits best your implementation.
I will introduce a last setting MockCoreSettings which will the base settings for every test classes so that we don’t rely on any org dependent setting. It’s important to keep it unchanged.

Callout Framework Settings
The Http Request Services CustomMetadataType will hold all resource settings used in any Callout. Here’s an example of how to configure one resource (from previous article):
Setting | Value | Description |
---|---|---|
Label | Heroku Service 01 | To identify the service |
Http request Service Name | HEROKU_S01 | The name used in your APEX code |
Service Id | SID-0001 | Id used to identify resource in test context |
Resource | /secured | The resource path |
Timeout | 120000 | Timeout to set in HttpRequest |
Primary Named Endpoint | CALLOUT:MyHeroku | CALLOUT: followed by the named credential |
Secondary Named Endpoint | CALLOUT:MySalesforce/services/apexrest | CALLOUT: followed by the named credential and path |
Bypass Primary Endpoint Permission | Leave it blank | Name of the custom permission to use bypass feature |
Force Secondary Endpoint | unChecked | Switch to Secondary endpoint if checked |
Custom Headers | {“bearer”:”{!$Credential.OAuthToken}”, “instanceurl”:”[your domain]”} | Special headers used in this callout in JSON |
We will need to test it as well, that’s why we need to introduce a non mutable setting TestingMockService:


Here again, we will fill every setting in order to test fully the Framework behaviors. This one will not work in real condition as the named credentials haven’t been specified.
Logging Event and Callout Tracking
When you deal with Callout, there are some Governor Limits that can be annoying and prevent us to do whatever we want in the way we want. And it’s really annoying when you want to only write once a feature to be reused automatically everywhere without coding any new line or without reminding developers to call the method at the end of a transaction for example.

Here, we see that DML is not allowed after a Callout has been made, it will break the current transaction. Generally, we will have to do all callouts first and then make all DML operations at last. Which imply to know how to deal with each callout result, store it somewhere and make the DML at last and re do it again for new developments, with the risk of someone adding a new Callout or DML in the middle that will mess everything. In this situation, I can’t log anything into custom table and I can’t track callout activities also in a generic way. It will be great to have some Pre-Transaction or Post-Transaction features to implement these but it doesn’t exist actually.
The challenge will be to make our callout, logging and tracking (success or failure) each one immediately and in a generic way and then processing any other callout or DML statements that are normally part of the transaction:

We will use Platform Event capabilities to overcome this situation by publishing a platform event instead of doing a direct DML call to a target SObject. Publishing an event is also considered as a DML operation but has a separate Governor Limits count from the standard DML operation. A second important point to keep in mind is that an event can be published Immediately or At the end of the transaction, we will choose Publish Immediately.
Publish Immediately means that Salesforce will create a new transaction not dependent from the source transaction and publish the event even if the source transaction is on failure. This behavior is a perfect match to log and track everything happening without breaking or be dependent from the source transaction.
In reality, the flow will be more like this:

In this example, every time my transaction start, I will call the Logging Framework then I will fire 2 callouts which will generate one Event each through the Callout Tracking Framework, then I will make some updates in database through DML operations and finally I will call the Logging Framework to notify that my transaction has ended.
It can be represented by something like this in code level:
//Calling the logging Framework
APU000_Logger.log(LoggingLevel.DEBUG, 'MyClass', 'MyMethod', 'My transaction 1 is starting');
ITF004_HttpRequestManager dm = new DM003_HttpRequestService('SFDC');
WRP003_HttpRequest inf = new WRP003_HttpRequest();
inf.requestType = 'GET';
inf.queryParameters = '/'+UserInfo.getUserId();
//making callout and calling the callout tracking Framework
dm.sendRequest(inf);
//Making Some DML
update accountsInList;
//Calling the logging Framework
APU000_Logger.log(LoggingLevel.DEBUG, 'MyClass', 'MyMethod', 'My transaction 1 is ending');
As you can see, the Callout Tracking Framework is embedded into the Callout Framework itself while the Logging Framework has to be used like a simple System.debug(…) statement. The number of event is not unlimited, that’s why it’s important to activate the event publishing only if you need to debug something.
Logging Framework Implementation and Testing
The Logging Framework is based on Platform Event LoggingMessage__e event object with Publish Option set to Publish Immediately:
Field | Description |
---|---|
ClassName__c | Name of the class from where the logger is called |
EndTime__c | Time of the logging event |
LogLevel__c | System.LoggingLevel (FINEST, ERROR, INFO, DEBUG, …) |
Message__c | Content to publish |
MethodName__c | Name of the method from where the logger is called |
StartTime__c | Time of the logging event |
TransactionId__c | System generated GUID |
UserId__c | Id of the connected User |
Username__c | Username of the connected User |
2 Custom Permission are also needed to control who can trigger the event:
Custom Permission | Behavior |
---|---|
EN_LOGGING | will enable logging feature for assigned users |
EN_LOGGING_EVENT | will enable logging event publishing for assigned users |
If the global setting Logging Enabled is checked or the custom permission EN_LOGGING is assigned then the message will be published in the Debug Log.
If the global setting Logging Event Enabled is checked or the custom permission EN_LOGGING_EVENT is assigned then the message will be published in Platform Event.
The logger can be called by simply typing this line:
APU000_Logger.log(LoggingLevel.DEBUG, 'MyClass', 'MyMethod', 'My Message');
Every other parameters are calculated by the framework on the fly and if you want to unit test it you can type these lines:
APU002_Context.bypassEventPublishingInTestContext = false;
String results = APU000_Logger.log(LoggingLevel.DEBUG, 'APU000_Logger_TEST', 'testLog', 'This is a test');
System.assertEquals('## >> [APU000_Logger_TEST][testLog][MESSAGE]: This is a test [EVENT PUBLISHED=true]', results);
APU002_Context.bypassEventPublishingInTestContext = true;
Callout Tracking Framework Implementation and Testing
The Callout Tracking Framework is based on Platform Event CalloutTracker__e event object with Publish Option set to Publish Immediately:
Field | Description |
---|---|
Endpoint__c | Endpoint URL of the service retrieved from Http Request Service settings |
EndTime__c | Request End Time calculated on the fly |
Headers__c | JSON Custom Headers of the service retrieved from Http Request Service settings |
HttpMethod__c | Type of the Http Request (GET, PUT, POST …) |
Message__c | Callout errors message if any |
QueryParameters__c | Parameters used in URL |
Resource__c | Name of the resource that was called retrieved from Http Request Service settings |
ServiceId__c | Id of the service retrieved from Http Request Service settings |
StartTime__c | Request Start Time calculated on the fly |
Status__c | Http Request Status after the request has received a reply |
StatusCode__c | Http Request Status Code after the request has received a reply |
Timeout__c | Http Request timeout value retrieved from Http Request Service settings |
TransactionId__c | System generated GUID |
UserId__c | Id of the connected User |
Username__c | Username of the connected User |
1 Custom Permission is also needed to control who can trigger the event:
Custom Permission | Behavior |
---|---|
EN_CALLOUT_TRACKING | will enable callout tracking for assigned users |
If the global setting Callout Tracking Enabled is checked or the custom permission EN_CALLOUT_TRACKING is assigned then the message will be published in the Platform Event.
Remember that Callout Tracking is embedded in the Callout Framework so you don’t need to call it explicitly. If you want to unit test, you can type these lines:
HttpRequestService__mdt service = HttpRequestService__mdt.getInstance('TestingMockService');
WRP003_HttpRequest wrpReq = new WRP003_HttpRequest();
wrpReq.endPoint = 'https://mock';
wrpReq.requestType = 'GET';
wrpReq.header.put('testH', 'testV');
wrpReq.queryParameters = '/Id/xxxx';
wrpReq.timeout = 120000;
APU002_Context.bypassEventPublishingInTestContext = false;
Boolean results = APU000_Logger.trackCallout(service, System.now(), System.now(), 'Sent', wrpReq);
System.assertEquals(true, results);
APU002_Context.bypassEventPublishingInTestContext = true;
Callout Framework Implementation and Testing
For each new resource to implement, you need to create 1 entry in Http Request Services with all the static and default configuration that is needed to call this resource.
Then you can use the Framework dynamic resolution classes or implement some extra customization by extending the Framework.
ITF004_HttpRequestManager requestManager = new DM003_HttpRequestService('MyNewResource');
Here, we will get a new instance to manage callout to this resource with all framework capabilities (Logging, Tracking …) and default behavior. The interface will give you access to 2 methods parseHttpResponse which can be overriden and sendRequest which will execute the callout. In case for any of your resource you need to implement special behavior to parse the response, you can write a new class extending DM003_HttpRequestService.
public inherited sharing class MyCustomImpl extends DM003_HttpRequestService implements ITF004_HttpRequestManager{
public MyCustomImpl(String serviceName){
super(serviceName);
}
public override void parseHttpResponse(WRP003_HttpRequest httpRequestInfos){
//WRITE YOUR OWN LOGIC FOR THESE SET OF RESOURCES
system.debug('overriden');
}
}
And then instantiate it like this to execute:
ITF004_HttpRequestManager requestManager = new MyCustomImpl('MyNewResource');
WRP003_HttpRequest wrpReq = new WRP003_HttpRequest();
wrpReq.requestType = 'GET';
requestManager.sendRequest(wrpReq);
It must display “overriden” in Debug Log.
In general, we will have to use 1 class per System to communicate with and not one class per resource, because each system will have their own implementation, constraints and behaviors that have to be handled specifically.
The WRP003_HttpRequest class is used as a container to send information through the Http Request, you can customize some information and most of them are calculated or retrieved from Http Request Services setting:
Variable | Description |
---|---|
body | Http Request Body if any (not used in GET) |
endPoint | EndPoint URL of the service retrieved from Http Request Service settings |
queryParameters | URL parameters to append if any |
requestType | Type of the Http Request (GET, PUT, POST …) |
timeout | Http Request timeout value retrieved from Http Request Service settings |
userName | username for Basic Authentication (just for testing purpose, use Named Credentials instead) |
password | password for Basic Authentication (just for testing purpose, use Named Credentials instead) |
certificate | SSL certificate name (just for testing purpose, use Named Credentials instead) |
header | Map add more header key/value if any |
httpRequest | Http Request that has been built |
httpResponse | Http Response from callout |
theException | Any exception that has been caught during the callout |
All these information are valuable, used by the Callout Framework and the Callout Tracking Framework, and can be used in the parseHttpResponse to handle the result of the callout and the expected behavior depending on the external system you are integrating with (ex: technical/functional error codes, exception…).
So now, what remains is the testing part. As you already know, it’s not possible to test Callout in Test Context, it will break your test unit and you won’t be able to cover your process if there at least one callout in it. To overcome this situation, we will use mocking to simulate the callout behavior.
You will start to write the Mock class like this:
public inherited sharing class TestingMock implements HttpCalloutMock{
public HttpResponse respond(HttpRequest req) {
HttpResponse res = new HttpResponse();
res.setStatus('OK');
res.setStatusCode(200);
res.setBody('This is a mock');
return res;
}
}
In my case it’s an inner class of my test class because I want to handle specific response depending on my test case. Now, I’m able to use to replace my real callout with this fake callout on the fly:
@isTest
private static void testSuccess(){
Test.setMock(HttpCalloutMock.class, new TestingMock());
Test.startTest();
ITF004_HttpRequestManager requestManager = new DM003_HttpRequestService('TestingMockService');
WRP003_HttpRequest wrpReq = new WRP003_HttpRequest();
wrpReq.requestType = 'GET';
requestManager.sendRequest(wrpReq);
System.assertNotEquals(null, wrpReq.httpResponse);
System.assertEquals('This is a mock', wrpReq.httpResponse.getBody());
System.assertEquals('OK', wrpReq.httpResponse.getStatus());
System.assertEquals(200, wrpReq.httpResponse.getStatusCode());
Test.stopTest();
}
As you can see, I didn’t touch my real implementation of the callout, all I need to do is to declare which class will be used as a Mock. So, in order to test full process in test context, you will only need to write the mock class and declare it like this in your test method:
Test.setMock(HttpCalloutMock.class, new TestingMock());
Sounds great? But wait !!! There is a big issue behind this approach! Imagine your process is making more than 1 callout to different endpoints, what do you think will happen? Salesforce will apply the same mock instance to all callout! Meaning, you won’t be able to have different responses for different services or use cases.
To overcome this, I will introduce the concept of multimocking. Let’s take the same example assuming we have 2 callout to 2 different systems.
I will create 2 inner classes like this in my test class to simulate 2 different responses:
public inherited sharing class TestingMock1 implements HttpCalloutMock{
public HttpResponse respond(HttpRequest req) {
HttpResponse res = new HttpResponse();
res.setStatus('OK');
res.setStatusCode(200);
res.setBody('This is a mock1');
return res;
}
}
public inherited sharing class TestingMock2 implements HttpCalloutMock{
public HttpResponse respond(HttpRequest req) {
HttpResponse res = new HttpResponse();
res.setStatus('OK');
res.setStatusCode(200);
res.setBody('This is a mock2');
return res;
}
}
Test.setMock(HttpCalloutMock.class, new TestingMock1()); can only be called once per test method so I cannot declare my second mock class. But with the MCK000_MultiRequestMock class, it’s now possible:
@isTest
private static void testSuccess(){
MCK000_MultiRequestMock multiMock = new MCK000_MultiRequestMock();
Test.setMock(HttpCalloutMock.class, multiMock);
multiMock.addRequestMock('SID-0000', new TestingMock1());
multiMock.addRequestMock('SID-0001', new TestingMock2());
Test.startTest();
ITF004_HttpRequestManager requestManager1 = new DM003_HttpRequestService('TestingMockService1');
WRP003_HttpRequest wrpReq1 = new WRP003_HttpRequest();
wrpReq1.requestType = 'GET';
ITF004_HttpRequestManager requestManager2 = new DM003_HttpRequestService('TestingMockService2');
WRP003_HttpRequest wrpReq2 = new WRP003_HttpRequest();
wrpReq2.requestType = 'GET';
requestManager1.sendRequest(wrpReq1);
requestManager2.sendRequest(wrpReq2);
System.assertNotEquals(null, wrpReq1.httpResponse);
System.assertEquals('This is a mock1', wrpReq1.httpResponse.getBody());
System.assertEquals('OK', wrpReq1.httpResponse.getStatus());
System.assertEquals(200, wrpReq1.httpResponse.getStatusCode());
System.assertNotEquals(null, wrpReq2.httpResponse);
System.assertEquals('This is a mock2', wrpReq2.httpResponse.getBody());
System.assertEquals('OK', wrpReq2.httpResponse.getStatus());
System.assertEquals(200, wrpReq2.httpResponse.getStatusCode());
Test.stopTest();
}
The MCK000_MultiRequestMock class will play as a router to the right implementation of each resource identified by the serviceId SID-XXX. It’s important that each resource has it unique ServiceId in order to identify it and make the match with the mock implementation.
How to deal with Platform Events ?
So far, you have learned how to use the Framework, implement your callout and unit test your processes. But we haven’t seen yet how to retrieve all platform events information. There are many possibilities to do so.
Platform Event-Triggered Flow:

In this example, every time a platform event is received, a flow will be triggered to send chatter notification in my feed with details from the event message.
Process Builder:

In this example, every time a platform event is received, a Process Builder will be triggered to send chatter notification in my feed with details from the event message.
Trigger:
trigger CalloutTrackerTrigger on CalloutTracker__e (after insert) {
//IMPLEMENT CUSTOM LOGIC HERE
}
Be aware that only after insert event is available. You can implement any custom logics to handle your event notification.
Lightning Component or Lightning Web Component using empAPI:
<aura:component implements="flexipage:availableForAllPageTypes" access="global" >
<lightning:empApi aura:id="empApi" />
<aura:handler name="init" value="{!this}" action="{!c.onInit}"/>
<aura:attribute name="subscription" type="Object" />
<aura:attribute name="results" type="List" />
<aura:attribute name="columns" type="List"/>
<div>
<div class="slds-card">
<lightning:button label="Subscribe" onclick="{! c.subscribe }" />
<lightning:button label="Unsubscribe" onclick="{! c.unsubscribe }" disabled="{!empty(v.subscription)}"/>
</div>
<div class="slds-card">
<lightning:datatable
keyField="TransactionId__c"
data="{! v.results }"
columns="{! v.columns }"
hideCheckboxColumn="true"/>
</div>
</div>
</aura:component>
({
// Sets an empApi error handler on component initialization
onInit : function(component, event, helper) {
component.set('v.columns', [
{label: 'Transaction Id', fieldName: 'TransactionId__c', type: 'text'},
{label: 'Start Time', fieldName: 'StartTime__c', type: 'datetime-local'},
{label: 'End Time', fieldName: 'EndTime__c', type: 'datetime-local'},
{label: 'User Id', fieldName: 'UserId__c', type: 'text'},
{label: 'User Name', fieldName: 'Username__c', type: 'text'},
{label: 'Class Name', fieldName: 'ClassName__c', type: 'text'},
{label: 'Method Name', fieldName: 'MethodName__c', type: 'text'},
{label: 'Message', fieldName: 'Message__c', type: 'text'},
{label: 'Log Level', fieldName: 'LogLevel__c', type: 'text'}]);
// Get the empApi component
const empApi = component.find('empApi');
// below line to enable debug logging (optional)
empApi.setDebugFlag(true);
},
// Invokes the subscribe method on the empApi component
subscribe : function(component, event, helper) {
// Get the empApi component
const empApi = component.find('empApi');
// Get the channel from the input box
const channel = '/event/LoggingMessage__e';
// Replay option to get new events
const replayId = -1;
// Subscribe to an event
empApi.subscribe(channel, replayId, $A.getCallback(eventReceived => {
// Process event (this is called each time we receive an event)
var resultList = component.get('v.results');
resultList.push(eventReceived.data.payload);
component.set('v.results', resultList);
console.log('Received event 1 ', eventReceived.data);
console.log('Received event2 ', eventReceived.data.payload);
}))
.then(subscription => {
// Confirm that we have subscribed to the event channel.
// We haven't received an event yet.
console.log('Subscribed to channel ', subscription.channel);
// Save subscription to unsubscribe later
component.set('v.subscription', subscription);
});
},
// Invokes the unsubscribe method on the empApi component
unsubscribe : function(component, event, helper) {
// Get the empApi component
const empApi = component.find('empApi');
// Get the subscription that we saved when subscribing
const subscription = component.get('v.subscription');
// Unsubscribe from event
empApi.unsubscribe(subscription, $A.getCallback(unsubscribed => {
// Confirm that we have unsubscribed from the event channel
console.log('Unsubscribed from channel '+ unsubscribed.subscription);
component.set('v.subscription', null);
}));
}
})
Add this component to the a new Lightning App and see the event messages filling the data table when you click on subscribe. This component has been customized to display logging message only when calling this line:
APU000_Logger.log(LoggingLevel.INFO,'MyClass', 'MyMethod','MyMessage');

There are many examples on Salesforce website to do it with LWC and it’s up to you to customize as you need.
Others:
You can also implement any other external listener (ex: Java, Mulesoft, …) compatible with Bayeux/CometD protocol.
Conclusion
In this article, we have learned how to setup the Callout, Logging, Tracking Framework and how to use them. We have introduced the concept of multimocking and we have also learned the concept of Platform Events and the way we can leverage it to special use cases like tracking and monitoring.
Hope you enjoy reading this article, see you soon for the next one ...