Search code examples
javabytecodeinstrumentationjava-bytecode-asmjavassist

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

    }

Solution

  • 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