Organizing Invocable And Static Code
Table of Contents:
Flows are increasingly a part of the Salesforce automation picture, and their seamless melding with Apex through Invocable Apex is part of the reason why admins and developers alike are embracing Flow. Learn how easy it is to make simple, reusable Apex invocables — and some of the design patterns that help you to structure them. The Salesforce Developer Documentation for invocables is still a bit hit or miss on this subject, and some of the things you might expect from invocables based on how the rest of Apex works may come as a surprise. Invocable methods have to be static — and because you can only have one per class, it’s not always easy to avoid repeating yourself; at best, it’s easy to find yourself with many static methods calling one another in an attempt to reuse code, which leaves the code tightly coupled and frequently harder to test as a result.
Invocable Apex Gotchas
Take, for example, the way invocable inputs and outputs are declared in Apex:
public class FlowInput {
@InvocableVariable(
label='Records to rollup'
description='Records to rollup to parent/related item(s)'
)
public List<SObject> recordsToRollup;
@InvocableVariable(
label='Rollup target\'s SObject Name'
description='The API Name of the SObject where the rollup value will be stored.'
required=true
)
public String rollupSObjectName;
// ... etc
}
public class FlowOutput {
public FlowOutput() {
this.isSuccess = true;
this.Message = 'SUCCESS';
}
@InvocableVariable(label='Is Success' description='Was rollup enqueued successfully?')
public Boolean isSuccess;
@InvocableVariable(label='Status Message' description='"SUCCESS" when isSuccess is true, otherwise the encountered error message')
public String message;
}
Right off the bat, it might appear that we have some solid built-ins exposed to us; first of all, the input/ouputs are classes. But they’re classes with … caveats:
- you can use constructors, as pictured
- you can assign default values through assignment (
public String rollupSObjectName = 'someString';
is all good) - you can’t keep common properties in a parent class and subclass to add additional invocable properties
- you can’t use getters/setters; no, I don’t know why
- you can use generic
SObject
s (like therecordsToRollup
property shown above) but the Flow engine (at least as of Winter ‘21) behaves differently than one might expect when it comes to the usage of the built-inGet Records
action — if there are no results retrieved fromGet Records
, for example, Flow assign’s that particular collection to null. That means that if you are passing records off to invocable Apex, you have to first check if the collection is null unless that particular property is an optional property - the description field for each invocable property does not, as of yet, get surfaced within Flow. You have to believe this is a feature that’s being worked on, but at present those description values can’t be seen anywhere other than the code. Bummer.
- you can only have one Invocable method per Apex class, and because the method has to be static, it can be difficult to structure your invocables (lacking dependency injection, it’s easy to end up with many tightly coupled objects)
Despite the quirks, Flow’s excellent performance (particularly within the newer Before Save record-triggered flows) has people excited about the prospect of sunsetting now-cumbersome Process Builders. There are … just a few issues:
- lack of support for proper debugging in record-triggered flows (this is a huge pain point)
- lack of support for subflows in record-triggered flows (makes it extremely difficult to avoid “Flow bloat” through repitition)
- somewhat limited scheduling support (selecting an SObject type in the scheduled Flow’s Start node creates a Flow Interview record for every record retrieved; if you don’t select a specific object here, only one Flow Interview is created per scheduled invocation)
- lack of support for mapping structures - if you have a scheduled flow that kicks off and grabs a bunch of contacts, associating those contacts to their accounts in any kind of sane way basically requires this sort of structure
The Challenges In Structuring Invocable/Static-based Apex
Firstly, reiterating the above — only one @InvocableMethod
is allowed per Apex class (and I’ll repeat this again, below):
@InvocableMethod(
category='Rollups'
label='Perform rollup on records'
description='Returns a single FlowOutput record with "SUCCESS" as
the message if async rollup operation is enqueued successfully, and the encountered error otherwise'
)
global static List<FlowOutput> performRollup(List<FlowInput> flowInputs) {
// method body
}
Key things to note when it comes to the method signature itself:
- again, the description isn’t surfaced within Flow itself (yet)
- the label ends up being what you search for within Flow
- as of Spring 21, you can use
callout=true
within the@InvocableMethod
parantheses to quickly wire up HTTP callouts from flow! - it’s bulkified by default — we don’t (and shouldn’t) operate under the assumption that there will only ever be one
FlowInput
variable in the list that’s passed in; the reason for this is because of how the Flow engine works to “boxcar” requests before they reach Apex. My informal tests on exactly how the Flow engine bulkifies requests being passed to Apex have shown that while the boxcarring isn’t quite as effective as you loading up Invocable methods with some kind of deferral mechanism that gets invoked at the end of the Flow (I use this exact pattern in Rollup to even further bulkify rollups being performed with the RollupFlowBulkProcessor class), it’s effective enough to justify all Invocables taking inList
type arguments! - the category is extremely useful — Apex actions are grouped by category (by default) within Flow:
For the consumers of the Invocable Apex you’re producing, ensuring all your @InvocableMethod
s are properly categorized is of the essence.
But that’s basically it for structuring your code in terms of how it gets surfaced within Flow. As a developer there are two challenges at the center of building non-bulky invocables:
- the fact that the method signature has to be static (now, if you want to keep your classes small, you have to take advantage of other public static methods in order to keep code reusable)
- aaaand we’re back to the fact that you can only have one
@InvocableMethod
per class. For every invocable you build, you’re adding at least one class to your class library (but I’d like to assume you’re also writing test code for your invocables, so let’s say two classes per Invocable added 😇)
When we look at the Gang of Four programming patterns, most of standard options for abstraction don’t apply; objects are supposed to be encapsulated; they’re supposed to be open to extension but closed to modification. Static gets in the way of that. There are, however, two patterns which do pair well with static:
- The singleton pattern
- The facade pattern (which I mentioned briefly in the Extendable API article). I won’t touch too much on facade here, but I recommend the Extendable API post for further reading since we’ll also be talking later in this post about how additional layers of indirection can help to solve problems, and the Facade pattern is the most straightforward version of indirection possible!
Static objects that don’t perform side-effects within their methods are typically exposed to callers as singletons. This pattern is routinely hated upon, because singletons tend to be abused, but the pattern certainly has its place and providing you with object-oriented options when it comes to working with invocable Apex is definitely one of them.
Consider the following:
public class InvocableExample {
public class FlowInput {
@InvocableVariable(
label='Records'
)
public List<SObject> records;
@InvocableVariable(
label='Match key'
required=true
)
public String matchKey;
@InvocableVariable(
label='Match value'
required=true
)
public String matchVal;
}
public class FlowOutput {
public FlowOutput() {
this.isSuccess = true;
this.matchingValues = new List<SObject>();
}
@InvocableVariable(label='Matching values')
public List<SObject> matchingValues;
}
@InvocableMethod(
category='Examples'
label='Get matching values'
)
public static List<FlowOutput> getMatchingValues(List<FlowInput> flowInputs) {
List<FlowOutput> outputs = new List<FlowOutput>();
for (FlowInput input : flowInputs) {
FlowOutput output = new FlowOutput();
if (input.records?.isEmpty() == false) {
for (SObject record : input.records) {
if (record.get(input.matchKey) == input.matchVal) {
output.matchingValues.add(record);
}
}
}
}
return outputs;
}
}
Iterating through Flow collections is one of the more interesting limits associated with the Flow engine — you can only execute 2000 elements in any given flow. That’s probably fine for most record-triggered flows, but for scheduled flows with complicated business logic, off-loading things like finding several record collections with different matching criteria can help (instead of just burning through your executed flow element limits while iterating). This example is contrived, of course; in many instances within the Flow engine, you’d be better off achieving the above functionality with different Get Records
actions with the appropriate SOQL filters.
With that being said, I think we’re all aware of how requirements for even simple bits of code like the above tend to get tweaked over time. It could be that the ask becomes using Custom Metadata or feature flags to help control which records qualify — whatever the case may be, when all of your code paths feature static methods calling one another, test setup tends to become more complicated. If your dependencies are tightly coupled due to the entire code path being static, you have little choice but to initialize values in your tests that may not even be relevant for the feature under test.
Returning to our Get matching values
example, above, the “simple” for loop is also hiding some interesting edge cases — for example, if the record passed in comes from the database, but the field from input.matchKey
hasn’t been included, an exception will be thrown. Because this for loop (that filters for records meeting a certain condition) is so commonplace, it would be great to abstract it. Indeed, for many years I used a class, CollectionUtils
to perform common helper tasks such as this (tests first!):
@IsTest
private class CollectionUtilsTests {
@IsTest
static void shouldReturnMatches() {
String matchValue = 'match value';
Account acc = new Account(Name = matchValue);
List<SObject> matches = CollectionUtils.getRecordsMatching(
new List<Account>{ acc },
Account.Name,
matchValue
);
System.assertEquals(
acc,
matches[0],
'Account should be returned when name matches'
);
}
@IsTest
static void shouldFilterOutNonMatches() {
Account acc = new Account(Name = 'Non matching value');
List<SObject> matches = CollectionUtils.getRecordsMatching(
new List<Account>{ acc },
Account.Name,
'something else'
);
System.assertEquals(true, matches.isEmpty());
}
@IsTest
static void shouldNotThrowExceptionForUnqueriedField() {
Account acc = new Account(Name = 'test insert');
insert acc;
acc = [SELECT Id FROM Account];
List<SObject> matches = CollectionUtils.getRecordsMatching(
new List<Account>{ acc },
Account.AnnualRevenue,
0
);
System.assertEquals(true, matches.isEmpty());
}
}
And the implementation:
public class CollectionUtils {
// other collection based methods, as well ...
// always prefer a strongly-typed version
// when you can get away with it ...
public static List<SObject> getRecordsMatching(List<SObject> inputs, SObjectField field, Object value) {
return getRecordsMatching(inputs, field.getDescribe().getName(), value);
}
public static List<SObject> getRecordsMatching(List<SObject> inputs, String fieldName, Object value) {
// use clone() to retain the "getSObjectType()" info
// from the passed-in list, in case downstream consumers
// need that
List<SObject> matchingInputs = inputs == null ? new List<SObject>() : inputs.clone();
matchingInputs.clear();
for (SObject input : inputs) {
Map<String, Object> populatedFields = input.getPopulatedFieldsAsMap();
if (populatedFields.containsKey(fieldName) && populatedFields.get(fieldName) == value) {
matchingInputs.add(input);
}
}
return matchingInputs;
}
}
All of that is well and good, and simple enough that in the case of the Get matching values
invocable, you’re nearly looking at an Apex one-liner:
public class InvocableExample {
public class FlowInput {
@InvocableVariable(
label='Records'
)
public List<SObject> records;
@InvocableVariable(
label='Match key'
required=true
)
public String matchKey;
@InvocableVariable(
label='Match value'
required=true
)
public String matchVal;
}
public class FlowOutput {
public FlowOutput() {
this.isSuccess = true;
this.matchingValues = new List<SObject>();
}
@InvocableVariable(label='Matching values')
public List<SObject> matchingValues;
}
@InvocableMethod(
category='Examples'
label='Get matching values'
)
public static List<FlowOutput> getMatchingValues(List<FlowInput> flowInputs) {
List<FlowOutput> outputs = new List<FlowOutput>();
for (FlowInput input : flowInputs) {
FlowOutput output = new FlowOutput();
output.matchingValues = CollectionUtils.getRecordsMatching(input.records, matchKey, matchVal);
}
return outputs;
}
}
The challenge lies, again, with any other modifications you’d like to make to this code. If you need to filter by more than one field, and particularly if you need to do something like an or statement (filter the items before this date, or after that one), you’ve now run into the primary challenge with procedurally-based code; you’re tied too tightly to your implementation to continue to make use of it (easily) in the face of changing requirements. Does that mean we’re back to re-implementing the for loop within InvocableExample
? Read on …
Using Singletons With Static/Invocable Code
With the problem statement established, let’s return to the idea I floated by you earlier — that singletons can help avoid tightly coupling your code together. Let’s see how:
public class InvocableExample {
// yada yada, all that good stuff
// featuring the FlowInput / FlowOutput
private final CollectionUtils collectionUtils;
private InvocableExample(CollectionUtils collectionUtils) {
this.collectionUtils = collectionUtils;
}
private static final InvocableExample self = new InvocableExample(
new CollectionUtils()
);
@InvocableMethod(
category='Examples'
label='Get matching values'
)
public static List<FlowOutput> getMatchingValues(List<FlowInput> flowInputs) {
List<FlowOutput> outputs = new List<FlowOutput>();
for (FlowInput input : flowInputs) {
FlowOutput output = new FlowOutput();
output.matchingValues = self.collectionUtils.getRecordsMatching(input.records, matchKey, matchVal);
outputs.add(output);
}
return outputs;
}
}
We’re taking very small steps. By delegating the declaration of CollectionUtils
to the constructor, we’ve opened up some interesting possibilities. At the moment, though, we’re still locked into the “is a” problem — only something that is a CollectionUtils
instance can be used in our constructor. There are two ways around that:
- Create and use an interface instead
- Make
CollectionUtils
virtual and override any implementation details we’d like (in this case, thegetRecordsMatching
method) by also making them virtual
In general, unless you have two disparate implementations that share common themes; are working with other internal teams and their objects with an agreed upon shape (frequently referred to as the object’s contract, or interface); or are working on a feature that you know in the near future will involve multiple layers of abstraction, making a class virtual is typically going to be the more lightweight option.
We’ll walk through both versions of how this might look, starting with the interface version.
Interface-based Dependency Injection
Oh yes. There it is — “dependency injection”. It’s taken me this long to introduce the topic within the confines of the conversation we’re having for a reason — for whatever reason, the concept of using dependency injection (or DI) to aid in the loose coupling of objects tends to scare people. Trust me — the myriad benefits you’ll experience in following this next section can be applied anywhere within the wider world of object-oriented programming (regardless of the language used);
public interface ICollectionUtils {
// omitting the SObjectField method (for simplicity)
List<SObject> getRecordsMatching(List<SObject> records, String matchKey, Object matchVal);
}
// and then the production-level class
public class CollectionUtils implements ICollectionUtils {
// note that these are no longer static
public List<SObject> getRecordsMatching(List<SObject> records, SObjectField matchKey, Object matchVal) {
return this.getRecordsMatching(records, matchKey.getDescribe().getName(), matchVal);
}
public List<SObject> getRecordsMatching(List<SObject> records, String matchKey, Object matchVal) {
List<SObject> matchingInputs = records == null ? new List<SObject>() : records.clone();
matchingInputs.clear();
for (SObject record : records) {
Map<String, Object> populatedFields = record.getPopulatedFieldsAsMap();
if (populatedFields.containsKey(matchKey) && populatedFields.get(matchKey) == matchVal) {
matchingInputs.add(record);
}
}
return matchingInputs;
}
}
So far, fairly subtle differences in the implementation which we’ll need to also reflect in our tests:
@IsTest
private class CollectionUtilsTests {
@IsTest
static void shouldReturnMatches() {
String matchValue = 'match value';
Account acc = new Account(Name = matchValue);
List<SObject> matches = new CollectionUtils()
.getRecordsMatching(
Account.Name,
matchValue
new List<Account>{ acc }
);
System.assertEquals(
acc,
matches[0],
'Account should be returned when name matches'
);
}
@IsTest
static void shouldFilterOutNonMatches() {
Account acc = new Account(Name = 'Non matching value');
List<SObject> matches = new CollectionUtils()
.getRecordsMatching(
Account.Name,
'something Else',
new List<Account>{ acc }
);
System.assertEquals(true, matches.isEmpty());
}
@IsTest
static void shouldNotThrowExceptionForUnqueriedField() {
Account acc = new Account(Name = 'test insert');
insert acc;
acc = [SELECT Id FROM Account];
List<SObject> matches = new CollectionUtils()
.getRecordsMatching(
Account.AnnualRevenue,
0,
new List<Account>{ acc }
);
System.assertEquals(true, matches.isEmpty());
}
}
Finally, we’ll return to the InvocableExample
class to finish making our changes:
public class InvocableExample {
// ...
@TestVisible
private static ICollectionUtils mockUtils;
private final ICollectionUtils collectionUtils;
private InvocableExample(ICollectionUtils collectionUtils) {
this.collectionUtils = collectionUtils;
}
private static InvocableExample self = new InvocableExample(
mockUtils == null ? new CollectionUtils() : mockUtils
);
@InvocableMethod(
category='Examples'
label='Get matching values'
)
public static List<FlowOutput> getMatchingValues(List<FlowInput> flowInputs) {
List<FlowOutput> outputs = new List<FlowOutput>();
for (FlowInput input : flowInputs) {
FlowOutput output = new FlowOutput();
output.matchingValues = self.collectionUtils.getRecordsMatching(input.records, input.matchKey, input.matchVal);
outputs.add(output);
}
return outputs;
}
}
If this is as far as you’re willing to go on the dependency injection refactoring train, you’ll still end up in a better position than when you started. Here’s why — the tests for InvocableExample
don’t have to exhaustively test combinations of how CollectionUtils
might work (they could be written in many different ways, for the record; this is just one example):
@IsTest
private class InvocableExampleTests {
private class FakeCollectionUtils implements ICollectionUtils {
private final Boolean shouldMatch;
// in general, passing Boolean values to functions/constructors
// is an anti-pattern. Typically what you would do instead is
// create two fake classes - one that returns matches, one that returns an empty list
public FakeCollectionUtils(Boolean shouldMatch) {
this.shouldMatch = shouldMatch;
}
public List<SObject> getRecordsMatching(List<SObject> records, String matchKey, Object matchVal) {
return this.shouldMatch ? records : new List<SObject>();
}
}
@IsTest
static void shouldFilterRecordsWhenTheyDoNotMatch() {
InvocableExample.mockUtils = new FakeCollectionUtils(false);
InvocableExample.FlowInput input = new InvocableExample.FlowInput();
inputs.records = new List<SObject>{ new Account() };
List<InvocableExample.FlowOutput> outputs = InvocableExample.getMatchingValues(
new List<InvocableExample.FlowInpout>{ input }
);
System.assertEquals(1, outputs.size());
System.assertEquals(true, outputs[0].atchingRecords.isEmpty());
}
@IsTest
static void shouldReturnMatchingRecords() {
InvocableExample.mockUtils = new FakeCollectionUtils(true);
InvocableExample.FlowInput input = new InvocableExample.FlowInput();
inputs.records = new List<SObject>{ new Account() };
List<SObject> matchingRecords = InvocableExample.getMatchingValues(
new List<InvocableExample.FlowInpout>{ input }
);
System.assertEquals(1, outputs.size());
System.assertEquals(1, outputs[0].matchingRecords.size());
System.assertEquals(new Account(), outputs[0].matchingRecords[0]);
}
}
The logic is simple — provide inputs, get outputs, validate that the outputs match what you expected. There’s a slight snag, however — and this isn’t just limited to the interface-based solution. The introduction of the mockUtils
test visible variable is, in fact, a code smell. Because we’re now responsible for object construction in InvocableExample
, we’ve had to introduce a ternary to deal with which implementation of CollectionUtils
will be constructed. The freedom we’ve been granted — testing only that the invocable works the way that we expect it to — has come with additional shackles. A ternary may seem OK, for the moment, but introduce even one other dependency (in the same way that we just introduced CollectionUtils
as a dependency, instead of a statically coupled class and method), and the constructor starts to get scarier:
// in InvocableExample
@TestVisible
private static ICollectionUtils mockUtils;
private final ICollectionUtils collectionUtils;
@TestVisible
private static ISomeOtherDependency mockSomeOtherDependency;
private final ISomeOtherDependency someOtherDependency;
private InvocableExample(ICollectionUtils collectionUtils, ISomeOtherDependency someOtherDependency) {
this.collectionUtils = mockUtils == null ? collectionUtils : mockUtils;
this.someOtherDepdency = mockSomeOtherDependency == null ? someOtherDependency : mockSomeOtherDependency;
}
private static InvocableExample self = new InvocableExample(new CollectionUtils(), new SomeOtherDependency());
Yikes.
This is the exact problem that I had run into years ago; in order to keep the tests fast and easy to read (ideally an individual test shouldn’t scroll beyond a desktop monitor’s viewport), dependency injection was the preferred tool — but how to accommodate for objects whose roles (and dependencies) tended to grow over time? We’ll pick up that thread in the next section. For now, let’s move on to covering the virtual class-based DI model.
Virtual Class-based Dependency Injection
Returning to CollectionUtils
, we abandon the idea of a separate interface:
public virtual class CollectionUtils {
public List<SObject> getRecordsMatching(List<SObject> records, SObjectField matchKey, Object matchVal) {
return this.getRecordsMatching(records, matchKey.getDescribe().getName(), matchVal);
}
public virtual List<SObject> getRecordsMatching(List<SObject> records, String matchKey, Object matchVal) {
List<SObject> matchingInputs = records == null ? new List<SObject>() : records.clone();
matchingInputs.clear();
for (SObject record : records) {
Map<String, Object> populatedFields = record.getPopulatedFieldsAsMap();
if (populatedFields.containsKey(matchKey) && populatedFields.get(matchKey) == matchVal) {
matchingInputs.add(record);
}
}
return matchingInputs;
}
}
And in the InvocableExample
test class:
private class FakeCollectionUtils extends CollectionUtils {
private final Boolean shouldMatch;
public FakeCollectionUtils(Boolean shouldMatch) {
super();
this.shouldMatch = shouldMatch;
}
public override List<SObject> getRecordsMatching(List<SObject> records, String matchKey, Object matchVal) {
return this.shouldMatch ? records : new List<SObject>();
}
}
// the test methods themselves stay the same - nice!
As you can see, there’s a lot less ceremony involved when it comes to overriding implementations. When the only place your overrides are going to occur are within specific tests, virtual classes tend to have less of an impact on the overall number of lines of code you’re responsible for maintaining; if you end up needing multiple different versions of an object with vastly different underlying implementations, the interface-based approach allows you to retain a common API without forcing you to make disparate objects conform to the same name & shape.
Inversion Of Control
Returning to the topic I deviated from while discussing the interface-based implementation: how do we create objects with the dependencies they need in both production and within our tests without code bloat? In Java and C#, the answer is simple: through inversion of control (IoC) containers. Put more simply, you register your production-level dependencies in a specific file; you tell your application: “at runtime, if I’m asking for an instance of ICollectionUtils
, here’s how you can find and construct that class.” Your tests are then free to override the baseline IoC instructions with more specific ones.
The example that I gave — with @TestVisible
backing variables — essentially does just that. Because we know it’s impossible for an outside caller of CollectionUtils
to set the mockUtils
variable, it’s safe to leave the overrides for each implementation in place. But it clutters up the production-level code; this clutter also grows linearly as your need to mock additional dependencies increases.
At this point, you might recall the David Wheeler quote from Lazy Iterators:
All problems in computer science can be solved using one more level of indirection. — David Wheeler
You might also recall the Factory pattern that I have spoken previously about, particularly in the context of mocking DML and performing SOQL queries. Here, at last, we come full circle to the Factory to show you, explicitly, how that one extra level of indirection can clean up your production-level code, leaving only the tests as the place where overrides occur. Using the Factory as an IoC container leaves you with one single place where overrides need to be performed, regardless of how many dependencies your objects end up having. Returning to InvocableExample
:
public class InvocableExample {
private InvocableExample() {
Factory factory = Factory.getFactory();
this.collectionUtils = factory.getCollectionUtils();
this.someOtherDepdency = factory.getSomeOtherDependency();
// etc - another benefit of this model is that your constructor doesn't need to change
// as additional objects are used
}
private static final InvocableExample self = new InvocableExample();
}
And the simple factory (leaving out the SOQL/DML-based mocking I’ve shown previously):
public virtual class Factory {
@TestVisible
private static Factory stubFactory;
protected Factory() {
// enforce "getFactory" as only way to call Factory
}
public static Factory getFactory() {
if (stubFactory != null) {
return stubFactory;
} else if (factory == null) {
factory = new Factory();
}
return factory;
}
public virtual CollectionUtils getCollectionUtils() {
return new CollectionUtils();
}
public virtual SomeOtherDependency getSomeOtherDependency() {
return new SomeOtherDependency(this);
}
}
A couple of things to note — for classes where a zero-argument constructor isn’t required (so — non-invocables, for one), you can have that object take in the Factory
class itself as the sole constructor argument (shown using the SomeOtherDependency
class, above). This removes the need to call Factory.getFactory()
within the constructor of an object itself; however, this doesn’t play nice with Type.forName
and generic Apex, so keep that in mind. That’s a bit off-topic, so let’s keep going with the example.
InvocableExampleTests
needs only a few changes to support the factory-based version of this code:
@IsTest
private class InvocableExampleTests {
shouldMatch = false;
// or the corresponding virtual version we showed above
private class FakeCollectionUtils implements ICollectionUtils {
public List<SObject> getRecordsMatching(List<SObject> records, String matchKey, Object matchVal) {
return shouldMatch ? records : new List<SObject>();
}
}
private class InvocableFactory extends Factory {
protected InvocableFactory() {
super();
}
public override ICollectionUtils getCollectionUtils() {
return new FakeCollectionUtils();
}
}
@IsTest
static void shouldFilterRecordsWhenTheyDoNotMatch() {
Factory.stubFactory = new InvocableFactory();
InvocableExample.FlowInput input = new InvocableExample.FlowInput();
inputs.records = new List<SObject>{ new Account() };
List<InvocableExample.FlowOutput> outputs = InvocableExample.getMatchingValues(
new List<InvocableExample.FlowInpout>{ input }
);
System.assertEquals(1, outputs.size());
System.assertEquals(true, outputs[0].atchingRecords.isEmpty());
}
@IsTest
static void shouldReturnMatchingRecords() {
shouldMatch = true;
Factory.stubFactory = new InvocableFactory();
InvocableExample.FlowInput input = new InvocableExample.FlowInput();
inputs.records = new List<SObject>{ new Account() };
List<SObject> matchingRecords = InvocableExample.getMatchingValues(
new List<InvocableExample.FlowInpout>{ input }
);
System.assertEquals(1, outputs.size());
System.assertEquals(1, outputs[0].matchingRecords.size());
System.assertEquals(new Account(), outputs[0].matchingRecords[0]);
}
}
Organizing Invocable & Static Code Closing
To review:
- we saw that
@InvocableMethod
Apex doesn’t have to mean locking yourself into static code all the way down, and how Singletons can help with this - we talked about how both interfaces and virtual classes can be used to separate our dependencies; how loosely coupling code helps to decrease test setup (not to mention test duplication!), lending clarity and speed to test runs
- we uncovered a potential issue using object stubs — that while
@TestVisible
private static variables can help us to decrease the complexity of our test code, it doesn’t help to reduce code bloat if every dependency needs to be swapped out - we briefly covered the topic of Inversion of Control, and used that as a convenient segueway into how the usage of a Factory class can consolidate where objects are being constructed so that your production-level code doesn’t need multiple constructors, ternaries in object creation, etc …
Things that we didn’t cover that the Factory
class also makes possible? SOQL mocking and DML mocking, for one — so if you’re interested in further applications of this pattern, definitely don’t miss out on the original article discussing dependency injection and the factory!
We took it slow, taking baby steps from: “this code does the same thing as code elsewhere in our codebase”, to: “you can mock dependencies in a variety of different ways, and now you know more about some of the patterns that help to make it possible.” You’ll note that though we started with the subject of how @InvocableMethod
s are used within Apex, we pretty quickly veered into more general matters; that’s because once you know how to create invocables, and you recognize that their biggest limitations are the static entry requirement and that you can’t have more than one per class, organizing the code it’s possible for your invocables to share becomes the priority. This is precisely why using the Singleton pattern to break out of static — and various methods we can employ to keep the code clean past that point — ends up being the central focus of this post.
If this article accomplishes nothing other than showing that complex things can be broken down into relatively simple pieces, I’d be well and truly satisfied. It represents years of work and effort to produce clean, vibrant code that runs quickly, is test-driven, and can be used around the world — with the singular intent of helping people reduce the inherent complexity that comes with producing and maintaining code. The repository where I've highlighted some of these patterns has been implemented and used in dozens of Salesforce orgs globally to reduce the complexity of object creation and consolidate where mocking occurs.
Special thanks to reader Erik B, who wrote in on 9 Mar 2021 asking me to touch on some of the topics here. I broke ground on this article a week later (and mentioned it in One Small Change), but it’s taken me more than two months to get back to, with the bulk of my attention going towards the open source work on Rollup these days. So — Erik, I owe you one, sorry it took me so long to get to this! Hopefully it was an enjoyable read.