我有一个类到服务器执行查询,我想做的是用模拟替换该调用(需要在后台运行实时服务器)为了单元测试的目的,调用返回一些预设响应。
我是BCEL的新手,我已经看了这篇文章,尽可能地调整它,但似乎无法让它为我工作:
Replacing statically referenced method in Java
这里有一些代码:
// =======================================================
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() );
/**
* <p>Redirect/replace calls to {@code ServerApi.evaluateQuery(String)} within the specified class to the specified static 'redirectTo' method in the specified 'redirectTo' class</p>
*
* @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 > 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() );
}
}
在我的单元测试开始时,我试图将呼叫替换为
中的ServerApi.evaluateQuery(String)
QueryCaller
调用
的课程MockServerApi.evaluateQuery(String)
其中evaluateQuery()方法都返回QueryResultSet类型的对象。
然而,当我运行它时(为了在此处发布它,我必须稍微修改此代码)我得到一个underFlow堆栈:
java.lang.VerifyError:JVMVRFY036堆栈下溢; 类=组织/富/ QueryCaller, method = $ ServerApi $ evaluateQuery(Ljava / lang / String;)Lorg / foo / QueryResultSet;,在java.lang.J9VMInternals.verifyImpl(Native Method)中的pc = 0 java.lang.J9VMInternals.verify(J9VMInternals.java:93)at java.lang.J9VMInternals.initialize(J9VMInternals.java:170)at at org.foo.TestClass.test(TestClass.java:110)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:88) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:55) 在java.lang.reflect.Method.invoke(Method.java:613)at org.junit.runners.model.FrameworkMethod $ 1.runReflectiveCall(FrameworkMethod.java:47) 在 org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) 在 org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) 在 org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) 在 org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) 在 org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27) 在org.junit.rules.TestWatcher $ 1.evaluate(TestWatcher.java:55)at at org.junit.rules.RunRules.evaluate(RunRules.java:20)at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)at at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) 在 org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) 在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 at org.junit.runners.ParentRunner.access $ 000(ParentRunner.java:53)at at org.junit.runners.ParentRunner $ 2.evaluate(ParentRunner.java:229)at at org.junit.runners.ParentRunner.run(ParentRunner.java:309)at at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) 在 org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) 在 org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) 在 org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) 在 org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) 在 org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
任何想法?
-GY
答案 0 :(得分:0)
所以,看起来PowerMockito就是这里使用的那个:
@RunWith( PowerMockRunner.class )
@PrepareForTest( ServerApi.class )
public class TestClass {
@Test
public void test() throws Exception {
// mock up
PowerMockito.mockStatic( ServerApi.class );
BDDMockito.given( ServerApi.evaluateQuery( "foo = bar" ) )
.willReturn( MockServerApi.evaluateQuery( "foo = bar" ) );
// run code under test
assertEquals( "The mocked value was not returned",
"mocked value",
QueryCaller.callQuery() );
}
}
答案 1 :(得分:0)
从上面的帖子发现JMockit,在我看来,使用起来非常简单直观:
public class TestClass {
@Test
public void test( @Mocked ServerApi serverApi ) throws Exception {
// Mock up any calls you expect to happen during the test
new Expectations() {{
ServerApi.evaluateQuery( "foo = bar" ); result = "mocked value";
}};
// run code under test
QueryCaller.callQuery();
// Verify what actually happened
new Verifications() {{
ServerApi.evaluateQuery( "foo = bar" ); times = 1; // verify only called once
}};
}
}