Let's say I have a typical fflib service class with a void method (Application.cls omitted for clarity)
public class MyService {
public static void doStuffInSitu(String someContext, MyWrapper wrapper){
service().doStuffInSitu(someContext,myWrapper);
}
private static IMyService service() { // service factory
return (IMyService) Application.Service.newInstance(IMyService.class);
}
}
public class MyServiceImpl implements IMyService { // concrete impl of service
public void doStuffInSitu(String someContext, MyWrapper wrapper) {
// a lot of complicated stuff goes here
wrapper.bar = 'someBarValue'; // set value in calling arg
return;
}
}
with inner type
public class MyWrapper {
public String bar;
public string foo;
}
all of this enclosed within some larger code path:
public class MyOrchestration{
public void doOrchestration() {
// do a bunch of stuff - set someMyWrapper.foo
MyService.doStuff('abc',someMyWrapper);
// do stuff that relies on myWrapper returning a value in .bar <== IMPORTANT
}
with testmethod
new MyOrchestration().doOrchestration();
Since I'm testing doOrchestration
, i want the service it calls (MyService.doStuffInSitu(..)
) to be mocked and return a mocked bar
value back in its calling arg as that is the way the service was written.
How do I do this?
Best Answer
Since the service method being mocked is a void method, you can't use the ApexMocks
thenReturn
. You can use thedoAnswer
method of ApexMocks. I'm assuming some familiarity with ApexMocks already. If using Amoss, consult its documentation.The normal setup for apex mocking except where noted on the line marked
doAnswer
Test method
The above is saying -- that when method
doStuffInSitu
of the injected mock Service (mockMyService
) is called with any String argument and any MyWrapper argument, return in the calling arg a value determined by the classMockMyServiceAnswer
.doAnswer
takes two arguments - an object that implementsfflib_Answer
and the object where the answer should be returned in one of its calling arguments (i.e., the "answer")So, let's look at the class that does the answering
That's it - you tell Apex Mocks via .doAnswer where your answering object is and let it return a value back to the mocked
MyServiceImpl
object's relevant argumentNotes
You can make your Answer class parameterized using typical dependency injection through its constructor so several test methods can mock different values answered in the
myWrapper.foo
variable thus changing the paths throughdoOrchestration
's business logicIf you had multiple methods within MyServiceImpl that returned values within its arguments, then you'd have multiple classes, each implementing fflib_Answer. Your stubbing would associate each method with a different
doAnswer
Answer object.You can stub multiple answers for the same method call by using matchers if the arguments passed to
doStuffInSitu
vary based on your business logic and unit test case.If your method being mocked is not a void method but also modifies values back into its arguments, you can use the
.thenAnswer(..)
method. This is well covered in Answering with ApexMocks by Enzo Deti.Of course, if void method
doStuffInSitu
returned a value rather than returning values through its argument, you wouldn't use ApexMocks.doAnswer(..)
but instead use.thenReturn(..)
.