Class MockRegister
The MockRegister class supports testing your service code with mock responses for external services calls when executing in a unit test context.
The MockRegister supports mocking responses for calls of the following classes:
Test mocking is an important practice in modern software development as it enables:
- test service code in isolation from other components
- developing your service code even before other services are available
- testing error handling modes which are difficult to achieve when calling real services
- enables testing in environments where external services may not be available, e.g. dev machine or a CI server
- enables build/CI tests to execute much faster than when calling external services
The way MockRegister works is that it registers mock response object in a thread local store which Transact will return when executing services code in a unit test context. The way this work is:
- Unit test code registers mock response for an external service call.
- Unit test code then calls service's invoke method.
- The invoked service now calls an external service.
- Transact finds a matching registered mock request object and returns the associated mock response object.
- Service code receives mock response object and performs its logic.
- Unit test code verifies that service code performed correctly for the given execution context.
To illustrate this see the concrete example HTTP service code below.
Mocking HTTP Calls
In this example we are developing a GetAccounts service which makes a HTTP request to an retrieve a list of accounts.
The HTTP endpoint configuration details are provide by the services SvcConn
object.
If the HTTP call return a non 200 (OK) status code then our service will return a validation error to the form.
public class GetAccounts { public Logger logger FuncResult invoke(FuncParam param) { SvcConn svcConn = param.svcDef.svcConn HttpResponse response = new GetRequest(svcConn.endpoint) .setBasicAuth(svcConn.username, svcConn.password) .execute() FormFuncResult result = new FormFuncResult() if (response.isStatusOK()) { result.data.accounts = response.getTextContent() } else { result.errors.add(new ValidationError("accountsSvcError")) } return result } }
Now to test our services handling of HTTP errors we will mock a HttpResponse
object returning a HTTP 501 Service Unavailable error. This response object is registered using the same
GetRequest
object that is used in the service call. Transact's mock object matching rules are discussed later.
public class GetAccountsTest extends TransactSDKUnitTest { @Test void testFunction() throws Exception { FuncParam funcParam = new MockVoBuilder().createFuncParam(TRIGGER_FORM_UPDATE, svcDef, testParams) // Create a matching GetRequest object SvcConn svcConn = svcDef.svcConn HttpRequest matchRequest = new GetRequest(svcConn.endpoint).setBasicAuth(svcConn.username, svcConn.password) // Create a mock response object HttpResponse mockResponse = new HttpResponse().setStatus(503) // Register the request object to return mock response object when a matching HTTP request is made new MockRegister().when(matchRequest).thenReturn(mockResponse) // Invoke service FormFuncResult result = (FormFuncResult) new ServiceInvoker(svcDef) .setLogger(logger) .invoke("funcParam", funcParam) // Test results logger.info result assert result.errors.get(0).errorKey == "accountsSvcError" } }
The key line in the code above is the MockRegister call which registers the HttpRequest object and associated mock response object.
new MockRegister().when(request).thenReturn(response);
When HttpRequest.execute()
it will check whether there is a registered
thread local HttpRequest object which semantically matches for current the HttpRequest object. If there is
the execute method will return the registered mock response object immediately and not perform the actual HTTP
request.
HttpRequest Matching
When Transact is looking for an mock response object registered with a request, it will test the registered request objects attributes against the currently executing actual HttpRequest object. The attributes which are matched on include:
- matching - request method (GET, POST, PUT, DELETE)
- matching - request URI
- matching - request message data
- mock request headers is a matching set or is a subset of actual request headers
- mock request parameters is a matching set or is a subset of actual request parameters
Mocking Service / Function Calls
The other scenario where the MockRegister class is useful is when one service or function is calling another service
and we need to mock the response of the second service or function. Service mocking is supported in the
ServiceInvoker
, FluentFuncInvoker
and GroovyServiceInvoker
classes.
To visualize this consider the following execution path:
Trigger -> Service A Service A -> Service Invoker Service Invoker -> Service B Service B -> returns response
To facilitate unit testing we use the MockRegister to skip calling Service B and instead return a test result object directly from the ServiceInvoker class:
Test Trigger -> Service A Service A -> Service Invoker Service Invoker -> return mock result
To illustrate scenario this we have Prefill Function below which is calling shared GroovyService to lookup some data from another system. The function code is provided below:
public class PrefillFunction { public Logger logger FuncResult invoke(FuncParam param) { Map params = [:] params.request = param.request String profileData = new GroovyServiceInvoker() .setServiceName("LoadProfile") .setClientCode(param.svcDef.clientCode) .invoke(params) Map profileMap = new GsonBuilder().create().fromJson(profileData, Map.class) FormFuncResult result = new FormFuncResult() result.data.profileData = profileMap; return result } }
To mock the Groovy Service call profileData result in our PrefillFunction unit test we register a mock result object
with the MockRegister. Note the registered service definition matches that used in the PrefillFunction
code.
public class PrefillFunctionTest { public Logger logger FuncResult invoke(FuncParam param) { FuncParam funcParam = new MockVoBuilder() .createFuncParam(FuncParam.TRIGGER_FORM_OPEN, Txn.FORM_SAVED, svcDef, testParams); SvcDef matchSvcDef = new SvcDef([ "name": "LoadProfile", "clientCode": svcDef.clientCode, ]) Map profileData = [ "firstName": "John", "lastName": "Smith", "email": "[email protected]" ] new MockRegister().when(matchSvcDef).thenReturn(profileData) // Invoke service FormFuncResult result = (FormFuncResult) new ServiceInvoker(svcDef) .setLogger(logger) .invoke("funcParam", funcParam); // Test results logger.info result assert result.data != null assert result.data.profileData == profileData } }
Service / Function Matching
Matching of the service or function being called is done using the registered SvcDef attributes. The registered SvcDef should have the same attributes which are used in the service call. These attributes include:
- name - the service name (required)
- version - the service version number (optional)
- clientCode - the service's organization client code (required)
Test Coverage
Typically your service unit test code will incorporate many @Test
case methods to executing different scenarios
to ensure you have adequate test coverage. A multi test method unit test is provided below.
public class GetAccountsTest extends TransactSDKUnitTest { @Test void testOk() throws Exception { // Test normal service call ... } @Test void testBadRequest() throws Exception { // Test HTTP 400 bad request ... } @Test void testServiceUnavailable() throws Exception { // Test HTTP 503 service unavailable ... } }
- Since:
- 5.1.7
-
Constructor Summary
-
Method Summary
Modifier and TypeMethodDescriptionstatic void
clear()
Clear the Mock Register of all mock values.static HttpResponse
getHttpResponse
(HttpRequest request) Return the HttpResponse registered for the given request, if any.static Object
getServiceResult
(String serviceName, com.avoka.core.util.ver.Version version, boolean isCurrentVersion, String clientCode) Return the pair of SvcDef/result object registered for the given service, if any.static Object
getServiceResult
(String serviceName, com.avoka.core.util.ver.Version version, boolean isCurrentVersion, String clientCode, Map paramsMap) Return the pair of SvcDef/result object registered for the given service, if any.static boolean
hasServiceResult
(String serviceName, com.avoka.core.util.ver.Version version, boolean isCurrentVersion, String clientCode) Check if there is a registered entry for service.static boolean
hasServiceResult
(String serviceName, com.avoka.core.util.ver.Version version, boolean isCurrentVersion, String clientCode, Map paramsMap) Check if there is a registered entry for service.thenReturn
(HttpResponse response) Resolves the previously set HttpRequest (via when(HttpRequest) and registers the given HttpResponse for it in the mock test request registry.thenReturn
(Object serviceResult) Resolves the previously set SvcDef (via when(SvcDef) and registers the given Object for it in the mock test service registry.when
(HttpRequest request) Set the mock test context request registry key (the HttpRequest).Set the mock test context service registry key (the SvcDef).
-
Constructor Details
-
MockRegister
public MockRegister()
-
-
Method Details
-
when
Set the mock test context request registry key (the HttpRequest). This value is used when thenReturn(HttpResponse) is called. This method in itself has no effect on the registry. Ensure to follow it up with thenReturn(HttpResponse).- Parameters:
request
- http request- Returns:
- the current MockRegister instance
-
when
Set the mock test context service registry key (the SvcDef). This value is used when thenReturn(Object) is called. This method in itself has no effect on the registry. Ensure to follow it up with thenReturn(Object).- Parameters:
svcDef
- service definition- Returns:
- the current MockRegister instance
-
thenReturn
Resolves the previously set HttpRequest (via when(HttpRequest) and registers the given HttpResponse for it in the mock test request registry.- Parameters:
response
- the HttpResponse that should be returned when a HttpRequest matching the stored request is processed.- Returns:
- the current MockRegister instance
-
thenReturn
Resolves the previously set SvcDef (via when(SvcDef) and registers the given Object for it in the mock test service registry.- Parameters:
serviceResult
- the result object that should be returned when ServiceInvoker.invoke is called on a service matching the stored SvcDef.- Returns:
- the current MockRegister instance
-
getHttpResponse
Return the HttpResponse registered for the given request, if any.- Parameters:
request
- the request (required)- Returns:
- the response registered for this request, or null if none was found.
-
getServiceResult
public static Object getServiceResult(String serviceName, com.avoka.core.util.ver.Version version, boolean isCurrentVersion, String clientCode) Return the pair of SvcDef/result object registered for the given service, if any. To distinguish if null is registered, please call hasServiceResult() beforehand.- Parameters:
serviceName
- service nameversion
- service version numberisCurrentVersion
- denotes if the version is currentclientCode
- service client code- Returns:
- the service / result object registered for this service, or null if none was found.
-
getServiceResult
public static Object getServiceResult(String serviceName, com.avoka.core.util.ver.Version version, boolean isCurrentVersion, String clientCode, Map paramsMap) Return the pair of SvcDef/result object registered for the given service, if any. To distinguish if null is registered, please call hasServiceResult() beforehand.- Parameters:
serviceName
- service nameversion
- service version numberisCurrentVersion
- denotes if the version is currentclientCode
- service client codeparamsMap
- service params map- Returns:
- the service / result object registered for this service, or null if none was found.
- Since:
- 17.10.6
-
hasServiceResult
public static boolean hasServiceResult(String serviceName, com.avoka.core.util.ver.Version version, boolean isCurrentVersion, String clientCode) Check if there is a registered entry for service.- Parameters:
serviceName
- service name (required)version
- service version numberisCurrentVersion
- denotes if the version is currentclientCode
- service client code- Returns:
- true if there is, otherwise false
-
hasServiceResult
public static boolean hasServiceResult(String serviceName, com.avoka.core.util.ver.Version version, boolean isCurrentVersion, String clientCode, Map paramsMap) Check if there is a registered entry for service.- Parameters:
serviceName
- service name (required)version
- service version numberisCurrentVersion
- denotes if the version is currentclientCode
- service client codeparamsMap
- service params map- Returns:
- true if there is, otherwise false
- Since:
- 17.10.6
-
clear
public static void clear()Clear the Mock Register of all mock values.
-