service-call-tracker
A method call marshaller for Java
service-call-tracker is a method call marshaller for Java. It can record method invocations and is able to stub their implementation by replaying previously recorded method calls. Created with the aim to provide more reliable test data in development and testing tiers of an enterprise application, service-call-tracker can be used to stub code to provide a set of predictable and always-available test data.
Install
The project artefacts are available on maven central.
<dependency>
<groupId>ch.abertschi.sct</groupId>
<artifactId>service-call-tracker-api</artifactId>
</dependency>
<dependency>
<groupId>ch.abertschi.sct</groupId>
<artifactId>service-call-tracker-impl</artifactId>
</dependency>
Getting started
In order to gain control over method invocations, service-call-tracker must be hooked into your code
and an instance of ch.abertschi.sct.api.invocation.InvocationContext
must be built.
There are various ways how to intercept method calls in Java such as:
- Around Advice with AspectJ
- Arquillian Extension for service-call-tracker
- Java Dynamic Proxy API
- Any bean container framework providing interceptors (ie. EJB, CDI)
Recording
In order to record calls, create an instance of ch.abertschi.sct.api.Configuration
, enable recording and
fire up ServiceCallTracker#invoke(config: Configuration)
.
Configuration config = new Configuration();
config.setRecordingEnabled(true);
config.setRecordingSource(new File("my-recordings.xml"));
ServiceCallTracker serviceCallTracker = new ServiceCallTracker(config);
// gain access to a method call and build an InvocationContext
InvocationContext currentCall = ...
// invoke method an records response to my-recordings.xml
Object result = serviceCallTracker.invoke(currentCall);
Replaying
Configuration config = new Configuration();
config.setReplayingEnabled(true);
config.setReplayingSource(new File("my-replayings.xml"));
ServiceCallTracker serviceCallTracker = new ServiceCallTracker(config);
// gain access to a method call and build an InvocationContext
InvocationContext currentCall = ...
// build respone from my-replayings.xml if currentCall previously recorded
Object result = serviceCallTracker.invoke(currentCall);
Data Storage
The default configuration marshalles method calls to a file of key-value pairs of <call>
. The method arguments placed in <request>
act as the key and their return value placed in <response>
acts as the value.
<payload>
sections within <request>
and <response>
contain the marshalled method calls.
<storage>
<calls>
<call>
<request>
<payload class="object-array">
<string>Peter Parker</string>
</payload>
</request>
<response>
<payload class="ch.abertschi.sct.domain.Customer">
<name>Peter Parker</name>
<yearOfBirth>1970</yearOfBirth>
<comment>I am spiderman!</comment>
</payload>
<stacktrace/>
<script/>
</response>
</call>
...
</calls>
</storage>
- This example unmarshalls the response payload object above if an intercepted method with the String argument Peter Parker is called.
Stacktrace
There is support to throw exceptions from <stacktrace>
as a response to a matching request.
This is useful to test some worse-case scenarios.
<call>
<request>
<payload class="object-array">
<string>Peter Parker</string>
</payload>
</request>
<response>
<stacktrace>
Exception in thread "main" java.lang.OutOfMemoryError: Something bad happened
at Main.main(StackTraceUnserialize.java:107)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
... 6 more;
</stacktrace>
<script />
</response>
</call>
- The example above throws an OutOfMemoryError with the given stacktrace if an intercepted method with the String argument Peter Parker is called.
The library stacktrace-unserialize is used to convert a stacktrace to a throwable.
Groovy Scripting
If a static stacktrace is not enough, a Groovy Shell can be fired up to build a response. Groovy code within <script>
is evaluated at runtime.
<call>
<request>
<payload class="object-array">
<string>Peter Parker</string>
</payload>
</request>
<response>
<stacktrace/>
<script>
<!CDATA[[
String msg = request.payload.string + " was not found because an error happened!"
throw new RuntimeException(msg)
]]>
</script>
</response>
</call>
- The example above throws a RuntimeException if an intercepted method with the String argument Peter Parker is called.
These global variable are available in your Groovy script.
Properties | Description |
---|---|
request.payload | Access fields of the current request |
response.payload | Access fields of the response payload tag |
stacktrace | The stacktrace as instance of Throwable if set in the stacktrace tag of the response |
system | Access Java system variables (i.e. system.mySystemVarName) |
env | Access environment variables |
You can set a script, a stacktrace and a payload as a response for a call. In your Groovy script you have access to the throwable instance of the stacktrace tag and the return object of the payload tag. Once you set a script, the stacktrace and payload tags are ignored and you need to return or throw an objects within the script.
The table below shows the priority of execution of these tags (high to low)
Priority | Tag |
---|---|
1 | script |
2 | stacktrace |
3 | payload |
EL Expressions
You can use the Java Expression Language to write expressions within the <payload>
sections of the <request>
and <response>
objects.
These objects are preconfigured:
Properties | Description |
---|---|
request.payload | Access fields of the current request |
response.payload | Access fields of the response payload tag |
system | Access Java system variables (i.e. system.mySystemVarName) |
env | Access environment variables |
<call>
<request>
<payload class="object-array">
<string>Peter Parker</string>
<int>1</int>
</payload>
</request>
<response>
<payload class="ch.abertschi.sct.domain.Customer">
<name>#{request.payload.string}</name>
<yearOfBirth>1970</yearOfBirth>
<comment>I am spiderman!</comment>
</payload>
</response>
</call>
Regular expressions (experimental)
In any field of the request payload, you can use regular rexpressions to alter the request matching behaviour.
The example below throws an exception for any request given. Calls with the lowest index in the file are checked first.
<call>
<request>
<payload>*.</payload>
</request>
<response>
<stacktrace>java.lang.Exception: I always throw an Exception</stacktrace>
</response>
</call>
Some common regular expressions are predefined and accessible with as #{regex.<name>}
.
Properties | Description |
---|---|
regex.any | Ignore field |
regex.numeric | Match only if field is numeric |
Who uses this
This project was originally created as a side project at AXA Winterthur to increase test automation. It is used in-house with the Arquillian Extension for service-call-tracker to integration test backend code. However, its copyright is not assigned to AXA Winterthur.
Licence
Apache 2.0