I am trying to build a mocking framework in java which fits to a specific requirement of a project.
The scenario is, I have a method
public String returnRandom(){
String randomString = this.randomGenerator.returnRandom()
}
The randomGenerator is a dependency of this class and is injected to the object only in runtime. Means it would be null if the object is created without the dependency injection framework.
During an isolation test, I want the replace assignment to the statement
this.randomGenerator.returnRandom();
with a method which returns a stray random value, say "Helloworld".
I was trying to use javassist.expr.FieldAccess for the same, using which I can replace the field to a no operation and method call can be modified using javassist.expr.MethodCall.
I am not able to figure out how to replace the field with a dummy or a no operation. Is this possible using java assist or should I go for a more low level bytecode manipulation like asm?
Note: I could achieve replacing of a method call which doesn't originate on a field using javassist.expr.MethodCall. For example if the above example is
public String returnRandom(){
String randomString = returnRandom();
}
I am able to replace as
public String returnRandom(){
String randomString = MockedString.getSampleRandom();
}
The best thing you can do is to create an interface for the random generator, say:
public interface RandomGenerator {
public String returnRandom();
}
Then your original random generator can implement this interface, and the class that uses a random generator can depend on a class with a RandomGenerator interface.
Once you have this, it is fairly straight-forward to test. You create a mock generator that does what you want:
public class MockRndGenerator implements RandomGenerator {
public String returnRandom() {
return "Helloworld";
}
}
and when you're testing, you inject this class instead of the original.
public class Demo {
public Demo (RandomGenerator rndGenerator) {
this.randomGenerator = rndGenerator;
}
public String returnRandom(){
String randomString = this.randomGenerator.returnRandom()
}
}
* UPDATE *
Since I can't add code in comments, here is the Mockito solution:
you can always use Mockito to avoid creating physical mocks, and then you can set up expectations and inspections on the way
class Test {
public static rndTest() {
RandomGenerator rnd = Mockito.mock(RandomGenerator.class);
Mockito.when(rnd.returnRandom()).thenReturn("Helloworld");
Demo = new Demo(rnd);
}
}
I could solve the problem using javassist.expr.MethodCall. Below is the expression editor class used for checking the feasibility.
This replaces the targetMethod call (methodcalltoReplace) with the code used to get a mock object.
new ExprEditor() {
@Override
public void edit(MethodCall m) throws CannotCompileException {
try {
if (m.where().getName().equals(sourceMethod)) {
if (m.getMethod().getName().equals(methodcalltoReplace)) {
if(lineNumberOfMethodcalltoReplace == m.getLineNumber()){
// The content of the hardcoded string can be replaced with runtime data
m.replace("$_ = ($r)"+"new com.nuwaza.aqua.sample.SampleForMethodInvocationFieldAccess().helloworld();");
}
}
}
} catch (NotFoundException e) {
e.printStackTrace();
}
super.edit(m);
}
For a detail documentation see, Javaassist tutorial, introspection and customization
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With