Testing Apex Callable Interface dependencies in Salesforce Unit Tests

A view on improving (test) code quality by implementing a Callable Testing framework.

Justus van den Berg
7 min readDec 16, 2023

The Apex Callable Interface is a hidden gem when it comes to building loosely coupled dependencies between classes or packages.
It might not be the holy grail of “dependency injection”, but having a simple interface that is commonly known amongst Salesforce developers is really powerful.

There are cases where you might have Apex code that is dependent on a “Callable” class that does not exist in your org during a unit test.
This can easily lead to code that is either ignored during the test execution or it results in a lot of “if” statements to make the tests work, making the code harder to read, understand and maintain.

These potential issues can easily be overcome by implementing a mocking framework specifically for Callables.
In this article I’ll explain my approach to testing callable methods in dependent classes. Basic knowledge of the callable interface helps but is not required.

My approach is not per definition best practice, but simply a way to standardize and give you some food for thought on what could work for your implementation.

In a previous article I described a simple framework for testing exceptions in Apex Unit Tests, I extended my Lightweight — Apex Unit Test Utility with a mock Callable class called “Clbl” in the “utl” namespace.

This class can be used to mock any type of response data for a class that implements the Callable interface.
I purposely kept the class name short as it is used relatively a lot and keeps your test code clean. But stick to whatever naming convention you’re comfortable with.

It’s important to note that this “framework” approach is only going to work if you have full control over the exposing class and the dependent class. It is essentially a way of creating managed a communication system between packages that is in your own control.

If you use Callables in a package development model, testing them properly should be part of your overall packaging strategy. Especially if you have multiple teams working on multiple loosely coupled packages.

TL;DR :: An Apex Unit Test Utility

I created an Apex Unit Test Utility where one of the features is the mocking of Callables. Throughout this article I’ll be referencing the methods from that utility.

The Github repository can be found here:
https://github.com/jfwberg/lightweight-apex-unit-test-util-v2

The managed package can installed using this link:
/packaging/installPackage.apexp?p0=04tP30000007oePIAQ

The unlocked package can installed using this link:
/packaging/installPackage.apexp?p0=04tP30000007og1IAA

Implementing the Mock Framework

Let’s start with an example: I have a class named “DataProvider” that exposes it’s methods through the “call()” interface method. And it looks like this:

/**
* @description Example class that implements the callable interface
* to expose methods dynamically
*/
public class DataProvider implements Callable {

// Data Methods
public Contact[] getContactList(String accountId, Integer limit){}
public Decimal getComplexCalculationResult(Decimal number1,
Decimal number2){}

// Interface method where all the magic happens
public Object call(String action, Map<String, Object> args) {}
}

If I want instantiate this class from a dependent class it will look something like this:

Callable dtPr = (Callable) Type.forName('DataProvider')?.newInstance();

This is great because the “DataProvider” class does not have to exist in the org and it can still compile and deploy the code :-)
To test this we will need to mock the Callable class instance.

You implement the “Clbl” mock in two different places: Firstly in your main Apex code at the point where the Callable is instantiated.
What we want to do here is when a unit test is running, return the mock instance of the Callable class. We can then control the mocked instance of the class from the unit test.
You can get to the mock instance using the following code snippet:

if(Test.IsRunningTest()){
return (Callable) utl.Clbl.getInstance();
}

It’s common to have a lazy loading getter method to get a Callable instance. You simply add the test mock code at the top and it’s only a single line of code inside the is running test code block.

/**
* @description Example of a class that calls Callable methods and we need
* need to specify the outcome during a test
*/
public class DependentClass{

/**
* @description A getter method that instanciates the DataProvider clas
* as a Callable. This is usually implemented using lazy
* loading but for simplicity I'll leave that part out.
*/
private Callable getDataProvider(){

// This is where the testing magic happens: When the test runs we
// do not return the Callable instance from this class, but we
// return the Callable instance from the testing framework
if(Test.isRunningTest()){
return (Callable) utl.Clbl.getInstance();
}

// Dynamically instatiate the callable class
return (Callable) Type.forName('utl.DataProvider')?.newInstance();
}
}

For the dependent class I created a class with the name “DependentClass” that has a method that gets a list of contacts (“getContactList”) and second method that returns a decimal (“getComplexCalculationResult”) to showcase how a single Callable can return multiple data types.
I added the “multiplyComplexCalculationResult()” method as a demonstration of a method that does something with a result from a callable method.

Note that the “call()” method is of the type “Object” and always has to be cast to the target type.

/**
* @description Example of a class that calls Callable methods and we need
* need to specify the outcome during a test
*/
public class DependentClass{

// The callable method (no body for brevity)
private Callable getDataProvider(){... see code above ...}

/**
* @description Method that calls the data provider and retrieves a
* list ofof contact records based on an account Id
* with a limit of 10
*/
Contact[] getContactList(){
return (Contact[]) this.getDataProvider().call(
'getContactList',
new Map<String,Object>{
'acccountId' => accountId,
'limit' => 10
}
);
}

/**
* @description Method that calls the data provider and executes a
* complex calculation on two numbers
*/
Decimal getComplexCalculationResult(){
return (Decimal) this.getDataProvider().call(
'getComplexCalculationResult',
new Map<String,Object>{
'number1' => 1.1,
'number2' => 2.2
}
);
}

/**
* @description Method that uses the value of a callable method
* and processes it further
*/
Decimal multiplyComplexCalculationResult(Decimal multiplier){
return this.getComplexCalculationResult() * multiplier;
}
}

Now we have an example of a dependent class with our mock implemented, it time to test these three methods. I’ll keep the test methods simple, but make sure to follow best practices. Proper testing is important. (I cannot say this enough)

The second part of implementing this mock is in the apex test methods. Here we configure the mock with the value we want it to return when is called when the method runs in a test context.

We do this with a method called “utl.Clbl.setActionResponse()”. This method takes the callable method name and the return value as parameters. The method signature looks like this:

utl.Clbl.setActionResponse(
String methodName,
Object returnValue
);

When we setup up our test methods we need to specify the output of the Callable method at the top. Now when the test runs we know exactly what value is returned.

For example we can set the result of the complex calculation to “42.19”:

// Set a response for the getComplexCalculationResult method
utl.Clbl.setActionResponse('getComplexCalculationResult', 42.19);

A full example the structure looks something like this:

  • We define the result data from the callable methods in variables
  • We set these variables in the “utl.Clbl.setActionResponse()” response methods
  • We assert that the when we call the the methods on the “DependentClass”, the response is equal to the mock response that we defined.
@IsTest
static testCallableMethods(){

// Create contact list that the Callable method returns
Contact[] contactList = new Contact[]{
new Contact(LastName = 'Smith'),
new Contact(LastName = 'Jones')
};

// Add the result of the complex calculation we want to test
Decimal complexCalculationResult = 42.19;

// Set a response for the getContactList method
utl.Clbl.setActionResponse(
'getContactList',
contactList
);

// Set a response for the getComplexCalculationResult method
utl.Clbl.setActionResponse(
'getComplexCalculationResult',
complexCalculationResult
);

// Now test that the getContactList method actually return our
// specified value
Assert.areEqual(
contactList,
DependentClass.getContactList(),
'Unexpected Contact List Value'
);

// Test that the getComplexCalculationResult returns what we specified
Assert.areEqual(
complexCalculationResult,
DependentClass.getComplexCalculationResult(),
'Unexpected Complex Calculation Response Value'
);
}

In practice you would probably not test methods that return a single value only for some extra code coverage. You will want to test any code that uses these values.
This mock gives you the freedom to create multiple predictable values for the same callable instance to properly test the logic.

A more realistic scenario is testing the “multiplyComplexCalculationResult()” method. In the below example we set the the result for the calculation to different values and test the logic in for multiple outcomes.

@IsTest
static testCallableMethods(){

// Set a response for the getComplexCalculationResult method
utl.Clbl.setActionResponse('getComplexCalculationResult', 42.19);

// Assert that 2*42.19 = 84.38
Assert.areEqual(
84.38,
DependentClass.multiplyComplexCalculationResult(2),
'Unexpected multiplication result'
);

// Update the response for the getComplexCalculationResult method
utl.Clbl.setActionResponse('getComplexCalculationResult', 19.42);

// Assert that 5*19.42 = 97.10
Assert.areEqual(
97.10,
DependentClass.multiplyComplexCalculationResult(5),
'Unexpected multiplication result'
);

}

Conclusion

Testing callables is not difficult and you could even ask yourself if a framework (if we can call it that) is really needed. I personally believe that if you use callables in your packaging strategy you should have a standardized way of implementing and testing them as well:
This will make the code easier to read and more maintainable and understandable.

Are there other ways to test Callables? Absolutely, but here is a simple standardised way with the minimal amount of code, good readability that can be used by anyone on your team.

Final notes

I hope this was insightful and gives some food for thought. I cannot state the importance of proper testing and not just reaching a coverage goal enough. As always feel free to leave any feedback.

At the time of writing I am a Salesforce employee, the above article describes my personal views and techniques only. They are in no way, shape or form official advice. It’s purely informative.
Anything from the article is not per definition the view of Salesforce as an Organization.

Cover image by Shahadat Rahman

--

--