title: Replace method call at runimte in JUnit test case using BCEL?
body: <p>I have a class that goes off to a server to perform a query, and what I'd like to do is replace that call (which requires a live server to be up and running in the background) with a mock call that returns some canned response for the purposes of unit testing.</p>
  
  <p>I'm brand new to BCEL and I've looked at this post, tweaked it as well as I can, but can't seem to get it working for me:</p>
  
  <p><a href="https://stackoverflow.com/questions/12119720/replacing-statically-referenced-method-in-java">Replacing statically referenced method in Java</a></p>
  
  <p>Here's some code:</p>
  
  <pre><code>    // =======================================================
  
      public class JUnitByteCodeUtils {
  
      public static final String EVAL_QUERY_CLASS_NAME_SHORT = "ServerApi";
      public static final String EVAL_QUERY_CLASS_NAME_FULL  = "org.foo." + EVAL_QUERY_CLASS_NAME_SHORT;
      public static final String EVAL_QUERY_METHOD_NAME      = "evaluateQuery";
      public static final String EVAL_QUERY_METHOD_SIGNATURE = "(Ljava/lang/String;)Lorg/foo/QueryResultSet;";
      public static final Type   QUERY_RESULTSET_TYPE        = new ObjectType( QueryResultSet.class.getName() );
  
      /**
       * &lt;p&gt;Redirect/replace calls to {@code ServerApi.evaluateQuery(String)} within the specified class to the specified static 'redirectTo' method in the specified 'redirectTo' class&lt;/p&gt;
       * 
       * @param classToRedirect  - Class containing calls to {@code ServerApi.evaluateQuery(String)}
       * @param redirectToClass  - The class containing the static method to be called instead
       * @param redirectToMethod - The static method to be called instead
       */
      public static void redirectQueryEvaluationCalls( String classToRedirect, String redirectToClass, String redirectToMethod ) {        
  
          JavaClass compiledClass;
          try {
            compiledClass = Repository.lookupClass( classToRedirect );
          } catch( ClassNotFoundException ex ) {
            throw new RuntimeException( "Unable to resolve class [" + classToRedirect + "]", ex );
          }
  
          // (2) Create a working class from the compiled class (that we can modify) 
          final ClassGen        workingClass = new ClassGen( compiledClass );
          final ConstantPoolGen constantPool = workingClass.getConstantPool();
  
          // (3) Locate the query evaluation method in the constant pool of the class to be modified 
          final int methodIdx = constantPool.lookupMethodref( EVAL_QUERY_CLASS_NAME_FULL, EVAL_QUERY_METHOD_NAME, EVAL_QUERY_METHOD_SIGNATURE );
  
          if( methodIdx &gt; 0 ) {
            final ConstantMethodref evalQueryMethodReference = (ConstantMethodref) constantPool.getConstant( methodIdx );
  
            evalQueryMethodReference.setClassIndex( constantPool.lookupClass( classToRedirect ) );
            evalQueryMethodReference.setNameAndTypeIndex( constantPool.addNameAndType( "$" + EVAL_QUERY_CLASS_NAME_SHORT + "$" + EVAL_QUERY_METHOD_NAME, 
                                                                                       EVAL_QUERY_METHOD_SIGNATURE ) 
                                                        );
  
            // (4) Build up some new byte code instructions to redirect the existing calls to some new target method
            final InstructionList    code        = new InstructionList();
            final InstructionFactory codeFactory = new InstructionFactory( workingClass, constantPool );
  
            code.append( codeFactory.createInvoke( redirectToClass, 
                                                   redirectToMethod, 
                                                   QUERY_RESULTSET_TYPE, 
                                                   new Type[] { Type.STRING }, 
                                                   Constants.INVOKESTATIC ) );
  
            code.append( codeFactory.createReturn( QUERY_RESULTSET_TYPE ) );
            code.setPositions();
  
            // (5) Replace the existing query evaluation calls with calls to our redirected method
            final MethodGen methodGen = new MethodGen( Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC | Constants.ACC_STATIC,
                                                       QUERY_RESULTSET_TYPE, 
                                                       new Type[] { Type.STRING }, 
                                                       new String[] { "query" }, 
                                                       "$" + EVAL_QUERY_CLASS_NAME_SHORT + "$" + EVAL_QUERY_METHOD_NAME, 
                                                       classToRedirect,
                                                       code, 
                                                       constantPool );
            // methodGen.setMaxLocals(0);
            // methodGen.setMaxStack(1);
            // methodGen.setMaxLocals();
            // methodGen.setMaxStack();
            workingClass.addMethod( methodGen.getMethod() );
  
            // (6) Write out the updated class definition
            try {
                File classFile = new File( Repository.lookupClassFile( compiledClass.getClassName() ).getPath() );
                workingClass.getJavaClass().dump( classFile.getPath() );
            } catch (final IOException ex) {
                throw new RuntimeException( "Unable to save updated class [" + classToRedirect + "]", ex );
            }
  
          } else {
              throw new RuntimeException( "Class [" + classToRedirect.getName() + "] does not contain any query evaluation calls" );
          }   
      }
  }
  
  
  // =======================================================
  
  public class QueryCaller {
  
      public QueryCaller() {} 
  
      public static String callQuery() {
          QueryResultSet result = ServerApi.evaluateQuery( "foo = bar" );
  
          return result.getValue();
      }
  }
  
  // =======================================================
  
  public class TestClass {
  
      @Test
      public void test() throws Exception {
          JUnitByteCodeUtils.redirectQueryEvaluationCalls( "org.foo.RelevanceCaller", 
                                                           "org.foo.MockServerApi", 
                                                           "evaluateQuery" );
  
          System.out.println( QueryCaller.callQuery() );
      }
  }
  </code></pre>
  
  <p>Here, at the start of my unit test, I'm trying to replace the call to </p>
  
  <blockquote>
    <p>ServerApi.evaluateQuery( String )</p>
  </blockquote>
  
  <p>within the </p>
  
  <blockquote>
    <p>QueryCaller</p>
  </blockquote>
  
  <p>class with a call to </p>
  
  <blockquote>
    <p>MockServerApi.evaluateQuery( String )</p>
  </blockquote>
  
  <p>where both of the evaluateQuery() method return an object of type QueryResultSet.</p>
  
  <p>However when I run this (and I've had to modify this code slightly for the purposes of publishing it here) I get a stack underFlow:</p>
  
  <blockquote>
    <p>java.lang.VerifyError: JVMVRFY036 stack underflow;
    class=org/foo/QueryCaller,
    method=$ServerApi$evaluateQuery(Ljava/lang/String;)Lorg/foo/QueryResultSet;, pc=0     at java.lang.J9VMInternals.verifyImpl(Native Method)    at
    java.lang.J9VMInternals.verify(J9VMInternals.java:93)     at
    java.lang.J9VMInternals.initialize(J9VMInternals.java:170)    at
    org.foo.TestClass.test(TestClass.java:110)    at
    sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)   at
    sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:88)
      at
    sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:55)
      at java.lang.reflect.Method.invoke(Method.java:613)     at
    org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
      at
    org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
      at
    org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
      at
    org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
      at
    org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
      at
    org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
      at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)  at
    org.junit.rules.RunRules.evaluate(RunRules.java:20)   at
    org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)     at
    org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
      at
    org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
      at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)  at
    org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)   at
    org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)     at
    org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)   at
    org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)  at
    org.junit.runners.ParentRunner.run(ParentRunner.java:309)     at
    org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
      at
    org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
      at
    org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
      at
    org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
      at
    org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
      at
    org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)</p>
  </blockquote>
  
  <p>Any ideas anyone?</p>
  
  <p>-GY</p>