While using the fflib framework since 2016 and having made significant enrichments to it (internally) since 2019, I’ve never come across the need to mock one method-signature with different responses, and therefore I was inclined to deep-dive into the possibilities with the open-source (apex-common) framework.
In this article I’d like to take you along the challenge, reasoning and how to properly implement/unlock yourself and utilising multiple stubproviders.
The challenge
One of the things Deloitte has extended the fflib framework with, is providing full support for Aggregate Results (Group by and SOQL functions like COUNT and SUM in selector queries); Both in the construction of SOQL queries in fflib_QueryFactory, as well in full supporting unit tests, mitigating the limitation of the inability to initiate AggregateResult.
When creating multiple stubs/mocks of the same class and defining different responses one will face the unfortunate situation that only the last definition will be applied to all implementations.
Below snippet showcases a simplified implementation of what we’re trying to achieve and what challenge we face. The .get() method is ‘defined’ twice, to different stub-instances (aggResult1, aggResult2). However, in the debug-line one can see the last ‘definition’ is applied to both.
// 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 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.
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 mocks being passed as stubprovider, the stub class is connected to the mocks variable, and calling mocks.mock() a second time for the same Type, will only cause a memory-reference to be returned, instead of creating a new Stub 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).
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 ).
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.