implement static method mock

This commit is contained in:
金戟 2020-10-27 09:22:42 +08:00
parent 989a52a048
commit 0ef13a8e95
5 changed files with 47 additions and 7 deletions

View File

@ -39,7 +39,7 @@ class DemoServiceTest {
} }
@TestableMock @TestableMock
private BlackBox secretBox() { private BlackBox secretBox(BlackBox _) {
return new BlackBox("not_secret_box"); return new BlackBox("not_secret_box");
} }
@ -91,6 +91,12 @@ class DemoServiceTest {
verify("startsWith").times(1); verify("startsWith").times(1);
} }
@Test
void should_able_to_mock_static_method() throws Exception {
assertEquals("not_secret_box", demoService.getBox().callMe());
verify("secretBox").times(1);
}
@Test @Test
void should_able_to_get_source_method_name() throws Exception { void should_able_to_get_source_method_name() throws Exception {
// synchronous // synchronous

View File

@ -28,7 +28,7 @@ class BlackBox(private val data: String) {
} }
object ColorBox { object ColorBox {
fun createBox(color: String): BlackBox { fun createBox(color: String, box: BlackBox): BlackBox {
return BlackBox("${color}_Box") return BlackBox("${color}_${box.callMe()}")
} }
} }

View File

@ -47,6 +47,13 @@ class DemoService {
return box.trim() + "__" + box.substring(1, 2) + "__" + box.startsWith("any") return box.trim() + "__" + box.substring(1, 2) + "__" + box.startsWith("any")
} }
/**
* Target 6 - method with static method invoke
*/
fun getBox(): BlackBox {
return ColorBox.createBox("Red", BlackBox.secretBox())
}
fun callerOne(): String { fun callerOne(): String {
return callFromDifferentMethod() return callFromDifferentMethod()
} }

View File

@ -1,9 +1,9 @@
package com.alibaba.testable.demo package com.alibaba.testable.demo
import com.alibaba.testable.core.accessor.PrivateAccessor import com.alibaba.testable.core.accessor.PrivateAccessor
import com.alibaba.testable.processor.annotation.EnablePrivateAccess
import com.alibaba.testable.core.annotation.TestableMock import com.alibaba.testable.core.annotation.TestableMock
import com.alibaba.testable.core.tool.TestableTool.* import com.alibaba.testable.core.tool.TestableTool.*
import com.alibaba.testable.processor.annotation.EnablePrivateAccess
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import java.util.concurrent.Executors import java.util.concurrent.Executors
@ -27,6 +27,16 @@ internal class DemoServiceTest {
@TestableMock @TestableMock
private fun startsWith(self: BlackBox, s: String) = false private fun startsWith(self: BlackBox, s: String) = false
@TestableMock
private fun secretBox(ignore: BlackBox): BlackBox {
return BlackBox("not_secret_box")
}
@TestableMock
private fun createBox(ignore: ColorBox, color: String, box: BlackBox): BlackBox {
return BlackBox("White_${box.callMe()}")
}
@TestableMock @TestableMock
private fun callFromDifferentMethod(self: DemoService): String { private fun callFromDifferentMethod(self: DemoService): String {
return if (TEST_CASE == "should_able_to_get_test_case_name") { return if (TEST_CASE == "should_able_to_get_test_case_name") {
@ -73,6 +83,13 @@ internal class DemoServiceTest {
verify("startsWith").times(1) verify("startsWith").times(1)
} }
@Test
fun should_able_to_mock_static_method() {
assertEquals("White_not_secret_box", demoService.getBox().callMe())
verify("secretBox").times(1)
verify("createBox").times(1)
}
@Test @Test
fun should_able_to_get_source_method_name() { fun should_able_to_get_source_method_name() {
// synchronous // synchronous

View File

@ -19,6 +19,7 @@ public class SourceClassHandler extends BaseClassHandler {
private final List<MethodInfo> injectMethods; private final List<MethodInfo> injectMethods;
private final Set<Integer> invokeOps = new HashSet<Integer>() {{ private final Set<Integer> invokeOps = new HashSet<Integer>() {{
add(Opcodes.INVOKESTATIC);
add(Opcodes.INVOKESPECIAL); add(Opcodes.INVOKESPECIAL);
add(Opcodes.INVOKEVIRTUAL); add(Opcodes.INVOKEVIRTUAL);
}}; }};
@ -60,7 +61,8 @@ public class SourceClassHandler extends BaseClassHandler {
// it's a member method and an inject method for it exist // it's a member method and an inject method for it exist
int rangeStart = getMemberMethodStart(instructions, i); int rangeStart = getMemberMethodStart(instructions, i);
if (rangeStart >= 0) { if (rangeStart >= 0) {
instructions = replaceMemberCallOps(cn, mn, instructions, node.owner, memberInjectMethodName, rangeStart, i); instructions = replaceMemberCallOps(cn, mn, memberInjectMethodName, instructions,
node.owner, node.getOpcode(), rangeStart, i);
i = rangeStart; i = rangeStart;
} }
} else if (ConstPool.CONSTRUCTOR.equals(node.name)) { } else if (ConstPool.CONSTRUCTOR.equals(node.name)) {
@ -123,6 +125,9 @@ public class SourceClassHandler extends BaseClassHandler {
case Opcodes.INVOKESTATIC: case Opcodes.INVOKESTATIC:
stackLevel += ClassUtil.getParameterTypes(((MethodInsnNode)instructions[i]).desc).size(); stackLevel += ClassUtil.getParameterTypes(((MethodInsnNode)instructions[i]).desc).size();
break; break;
case -1:
// reach LineNumberNode or LabelNode
return i + 1;
default: default:
stackLevel -= BytecodeUtil.stackEffect(instructions[i].getOpcode()); stackLevel -= BytecodeUtil.stackEffect(instructions[i].getOpcode());
} }
@ -153,13 +158,18 @@ public class SourceClassHandler extends BaseClassHandler {
ClassUtil.toByteCodeClassName(classType); ClassUtil.toByteCodeClassName(classType);
} }
private AbstractInsnNode[] replaceMemberCallOps(ClassNode cn, MethodNode mn, AbstractInsnNode[] instructions, private AbstractInsnNode[] replaceMemberCallOps(ClassNode cn, MethodNode mn, String substitutionMethod,
String ownerClass, String substitutionMethod, int start, int end) { AbstractInsnNode[] instructions, String ownerClass,
int opcode, int start, int end) {
mn.maxStack++; mn.maxStack++;
MethodInsnNode method = (MethodInsnNode)instructions[end]; MethodInsnNode method = (MethodInsnNode)instructions[end];
String testClassName = ClassUtil.getTestClassName(cn.name); String testClassName = ClassUtil.getTestClassName(cn.name);
mn.instructions.insertBefore(instructions[start], new FieldInsnNode(GETSTATIC, testClassName, mn.instructions.insertBefore(instructions[start], new FieldInsnNode(GETSTATIC, testClassName,
ConstPool.TESTABLE_INJECT_REF, ClassUtil.toByteCodeClassName(testClassName))); ConstPool.TESTABLE_INJECT_REF, ClassUtil.toByteCodeClassName(testClassName)));
if (Opcodes.INVOKESTATIC == opcode) {
// append a null value if it was a static invoke
mn.instructions.insertBefore(instructions[start], new InsnNode(ACONST_NULL));
}
mn.instructions.insertBefore(instructions[end], new MethodInsnNode(INVOKEVIRTUAL, testClassName, mn.instructions.insertBefore(instructions[end], new MethodInsnNode(INVOKEVIRTUAL, testClassName,
substitutionMethod, addFirstParameter(method.desc, ownerClass), false)); substitutionMethod, addFirstParameter(method.desc, ownerClass), false));
mn.instructions.remove(instructions[end]); mn.instructions.remove(instructions[end]);