At Deloitte we’ve been applying the fflib framework (apex-common) in all our Salesforce projects since 2016. Over time significant enrichments have been (internally) made, including full support for aggregate results. In this context, I recently faced the need to mock multiple different aggregate results from a query responses, which isn’t as straightforward as one would think.
In this article I’ll take you along the challenge, reasoning and how to properly enable the ability to mock different responses for the same method-signature on the same class-type, utilising multiple stubproviders.
Our AggregateResults support enables Group by and SOQL functions (like COUNT and SUM) in selector queries. Both in query-construction and unit-test mocking (mitigating the Apex restriction to initiate an AggregateResult). Interested? Have a look at the end of this article.
The challenge
The fflib framework provides an elegant way to ‘fake’ method response in unit tests. Advocating the single-responsibility principle and preventing the need to create actual test data, and thus realize a significant speed improvement of unit tests.
When defining multiple mocks (fake method responses) to the same class and methods, one will face the unfortunate situation that only the last definition will be applied to all implementations.
Concretely, as can be seen in the snippet below, the .get() method is ‘defined’ twice, to different stub-instances (aggResult1, aggResult2). One might initially expect both classes to return their own implementation. However, as seen in the debug-output, the last ‘definition’ is applied to both.
This, thus, impedes the ability to cover a unit test scenario where multiple aggregate results should be returned from the database, with different values.
// Unit Test - mock creation
fflib_ApexMocks mocks = new fflib_ApexMocks();
fflib_AggregateResult aggResult1 = ( fflib_AggregateResult ) mocks.mock( fflib_AggregateResult.class );
fflib_AggregateResult aggResult2 = ( fflib_AggregateResult ) mocks.mock( fflib_AggregateResult.class );
mocks.startStubbing();
mocks.when( aggResult1.get( 'expr0' ) ).thenReturn( 10 );
mocks.when( aggResult2.get( 'expr0' ) ).thenReturn( 5 );
mocks.stopStubbing();
System.debug( '*** '+aggResult1.get( 'expr0' ) + ' vs. '+ aggResult2.get( 'expr0' ) );
// Result
// DEBUG: *** 5 vs. 5
The cause/reason
When terms like mocking and stubbing are relatively new to you, and you would like to understand them better, I'd suggest to first scan through the theoretical part of my [previous post regarding unit tests and mocking](https://deloitte-engineering.github.io/2022/extensive-test-mocking-example-guide-in-fflib/).
When calling mocks.mock(), the fflib framework will call the standard Apex createStub method, providing the fflib_ApexMocks instance (mocks variable) as the stubprovider (see below). This creates a stubbed/mocked version of that class instance, of which void methods will do nothing, and non-void methods will return null.
public Object mock( Type classToMock ){
return Test.createStub( classToMock, this );
}
Due to this, when calling mocks.mock( fflib_AggregateResult.class ) twice on the same mocks thus doesn’t create two distinct stubbed instances of fflib_AggregateResult. The first call will create a stubbed instance, but the second method-call will simply return the memory-reference to the first instance.
While one might hope/expect different definitions to be possible on the stubbed-classes, in reality aggResult1 === aggResult2 and therefore the last definition ‘wins’/‘is applied’.
The solution
To allow different implementations for the same stubbed Type, one would need to create different stubproviders, to ensure each mocks.mock() will instantiate an own stubbed instance, instead of merely being a duplicate reference.
Luckily this can be easily done by instantiating multiple fflib_ApexMocks instances, and thus multiple stubproviders. After which it is crucial that the .mock(), .start/.stopStubbing() and .when().thenReturn() are called on the correct stubprovider. Other than that, the solution isn’t that complex 🙂
@IsTest
private static void fetchAccountWithHighestDurationSumPerWorkOrderId() {
// create three mock-stubproviders to allow distinct method returns for identical method calls of aggregate results
fflib_ApexMocks mocks = new fflib_ApexMocks();
fflib_ApexMocks mocks2 = new fflib_ApexMocks();
fflib_ApexMocks mocks3 = new fflib_ApexMocks();
SEL_WorkOrderLineItems m_selWOLIs = ( SEL_WorkOrderLineItems ) fflib.selector.defineMock( mocks, SEL_WorkOrderLineItems.class, WorkOrderLineItem.SObjectType);
fflib_AggregateResult aggResultWO1 = ( fflib_AggregateResult ) mocks.mock( fflib_AggregateResult.class );
fflib_AggregateResult aggResultWO2a = ( fflib_AggregateResult ) mocks2.mock( fflib_AggregateResult.class );
fflib_AggregateResult aggResultWO2b = ( fflib_AggregateResult ) mocks3.mock( fflib_AggregateResult.class );
// Given - set data
Id wo1 = fflib_IDGenerator.generate( WorkOrder.SObjectType );
Id wo2 = fflib_IDGenerator.generate( WorkOrder.SObjectType );
Id pa1 = fflib_IDGenerator.generate( Account.SObjectType );
Id pa2 = fflib_IDGenerator.generate( Account.SObjectType );
List<Id> inputIds = new List<Id>{ wo1, wo2 };
// set mocks
mocks.startStubbing();
mocks.when( m_selWOLIs.selectSumDurationPerPayingAccount( ( Set<Id> ) fflib_Match.anyObject())).thenReturn( new List<fflib_AggregateResult>{ aggResultWO1, aggResultWO2a, aggResultWO2b } );
mocks.when( aggResultWO1.get( 'WorkorderId' ) ).thenReturn( wo1 );
mocks.when( aggResultWO1.get( 'Paying_Account__c' ) ).thenReturn( pa1 );
mocks.when( aggResultWO1.get( 'expr0' )).thenReturn( 5.5 );
mocks.stopStubbing();
mocks2.startStubbing();
mocks2.when( aggResultWO2a.get( 'WorkorderId' ) ).thenReturn( wo2 );
mocks2.when( aggResultWO2a.get( 'Paying_Account__c' ) ).thenReturn( pa1 );
mocks2.when( aggResultWO2a.get( 'expr0' ) ).thenReturn( 10 );
mocks2.stopStubbing();
mocks3.startStubbing();
mocks3.when( aggResultWO2b.get( 'WorkorderId' ) ).thenReturn( wo2 );
mocks3.when( aggResultWO2b.get( 'Paying_Account__c' ) ).thenReturn( pa2 );
mocks3.when( aggResultWO2b.get( 'expr0' ) ).thenReturn( 10 );
mocks3.stopStubbing();
// When - perform test
List<Id> payingAccountIds = Flow_GetAccountWithHighestDuration.fetchAccountWithHighestDurationSumPerWorkOrderId( inputIds );
// Then - validate wo1 had highest and wo2 as null as equal value
System.Assert.areEqual( inputIds.size(), payingAccountIds.size(), 'Expected same number response as input' );
System.Assert.areEqual( pa1, payingAccountIds[ 0 ], 'Expected first WO Id to return PA1 as only one, being the highest' );
System.Assert.areEqual( null, payingAccountIds[ 1 ], 'Expected second WO Id to return null, as two Paying Accounts have same sum' );
}
Above snippet is a real example from one of my projects, unit testing an Invocable method which should return the Paying Account which has the highest sum of durations over child-WorkOrderLineItems. When two or more Paying Accounts have the same duration-sum, and being the highest, no Paying Account should be returned.
I deliberately chose to include this complete example, instead of trimming it down, as I believe real scenarios often provide so much more insights in how to use it (both for humans and LLMs).
Conclusion
In short, it is possible to realize different responses for the same method-signature on one class-type. To do this, one needs to provide different stubproviders (fflib_ApexMocks instances) and ensure mocking and stubbing is applied to the correct stubprovider.
The Deloitte fflib version
When you’d like to better understand the construction and support for AggregateResults, or specific references like the significantly simplified defineMock(), please feel free to reach out. Early 2026 we’re planning to share our version to a wider audience and I’m more than happy to already share some sneak peeks or make sure you’re the first to know.
In our version we've put emphasis on enabling developers in common pitfalls, but also reducing boilerplate code. defineMock() is a nice example as this centralizes the three method calls (mocks.mock(), stubbing-of-SObjectType and the setMock() into one, while standardizing the way how mocks are set ( e.g. fflib.unitofwork.defineMock() instead of new fflib_SObjectMocks.SObjectUnitOfWork( mocks ).