After building up the test suite, the TestWebServiceMockImpl became rather lengthy with many nested if elses. This is unfortunately necessary because only one mock class can be set per test method and some test methods invoke multiple web service callouts. Reviewing the code made me think… ewwww, this stinks with a nasty code smell…. How can this be improved? Answer: A mini framework.
Apex WebService Callout Test Mini Framework
We start by creating a Responder interface that is responsible for creating response objects for a given request.
public interface ITestWebServiceResponder {
object createResponse(
Object stub,
Object request,
String endpoint,
String soapAction,
String requestName,
String responseNS,
String responseName,
String responseType);
}
The Executor class is the only class that implements WebServiceMock and is used throughout our test suite. It’s responsible for executing the responders to generate the appropriate response for any given web service callout.
global class TestWebServiceMockExecutor implements WebServiceMock {
// Use simple lookup table to grab the responder indexed by request name
Map<String, ITestWebServiceResponder> responders =
new Map<String, ITestWebServiceResponder>{
‘Request_a’, TestWebServiceAResponder,
‘Request_b’, TestWebServiceBResponder,
// other responders here
};
global TestWebServiceMockExecutor() { }
// Use constructor dependency injection to allow callers to add additional
// responders or replace existing ones.
global TestWebServiceMockExecutor(
Map<String, ITestWebServiceResponder> otherResponders){
responders.putAll(otherResponders);
}
global void doInvoke(
Object stub,
Object request,
Map<String, Object> response,
String endpoint,
String soapAction,
String requestName,
String responseNS,
String responseName,
String responseType) {
// Grab the responder to use by request name.
ITestWebServiceResponder responder = responders.get(requestName);
// Responder creates the web service response.
Object response_x = responder.createResponse(
stub,
request,
endpoint,
soapAction,
requestName,
responseNS,
responseName,
responseType
);
// Put the response in the response object
response.put(‘response_x’, response_x);
}
// Responder class can be nested within the test class or be a separate class in the org.
@isTest
public class TestWebServiceAResponder implements ITestWebServiceResponder {
public object createResponse(
stub,
request,
endpoint,
soapAction,
requestName,
responseNS,
responseName,
responseType){
ResponseElement1 re1 = new ResponseElement1();
re1.property1 = some_value;
return re1;
}
}
Now, our test classes simply use the Executor class for the mock class and we’re all done.
@isTest
private class TestWebServiceCallout {
static testmethod void webServiceCallout1Test(){
Test.setMock(WebServiceMock.class, new TestWebServiceMockExecutor());
// Test code that invokes webservice callout 1
}
static testmethod void webServiceCallout2Test(){
Test.setMock(WebServiceMock.class, new TestWebServiceMockExecutor());
// Test code that invokes webservice callout 1 and 2.
}
}
Advantages
- Executor class is easily extensible by creating additional responder classes and either adding them to the responders map or passing them in as needed when creating the Executor.
- Executor class doesn’t have deep if else spaghetti code.
- Responder classes developed independently allowing us to follow the Single Responsibility Principle.
Disadvantages
Happy Coding,
Luke
Original Test Code (Ewwww)
global void doInvoke(
Object stub,
Object request,
Map<String, Object> response,
String endpoint,
String soapAction,
String requestName,
String responseNS,
String responseName,
String responseType) {
if (requestName == ‘request_a’){
ResponseElement1 re1 = new ResponseElement1();
re1.property1 = some_value;
response.put(‘response_x’, re1);
}
else if (requestName == ‘request_b’){
ResonseElement2 re2 = new ResponseElement2();
re2.property1 = some_value;
re2.property2 = some_other_value;
response.put(‘response_x’, re2);
}
// other responses handled.
}
}
And my test class looked like:
@isTest
private class TestWebServiceCallout {
static testmethod void webServiceCallout1Test(){
Test.setMock(WebServiceMock.class, new TestWebServiceMockImpl());
// Test code that invokes webservice callout 1
}
static testmethod void webServiceCallout2Test(){
Test.setMock(WebServiceMock.class, new TestWebServiceMockImpl());
// Test code that invokes webservice callout 1 and 2.
}
}