Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replacing a java method invocation from a field with a method call

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();

    }
like image 310
arunvg Avatar asked Mar 22 '26 10:03

arunvg


2 Answers

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);
  }
}
like image 142
meza Avatar answered Mar 24 '26 23:03

meza


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

like image 28
arunvg Avatar answered Mar 25 '26 01:03

arunvg