support mock invoke by interface instance

This commit is contained in:
金戟 2020-10-27 20:00:25 +08:00
parent 36136e878e
commit ae134ac5ce
9 changed files with 87 additions and 15 deletions

View File

@ -1,14 +1,19 @@
package com.alibaba.testable.demo; package com.alibaba.testable.demo;
public class BlackBox { public class BlackBox implements Box {
private String data; private String data;
@Override
public void put(String something) {
data = something;
}
public BlackBox(String data) { public BlackBox(String data) {
this.data = data; this.data = data;
} }
public String callMe() { public String get() {
return data; return data;
} }

View File

@ -0,0 +1,7 @@
package com.alibaba.testable.demo;
public interface Box {
void put(String something);
}

View File

@ -30,7 +30,7 @@ public class DemoService {
*/ */
public String newFunc() { public String newFunc() {
BlackBox component = new BlackBox("something"); BlackBox component = new BlackBox("something");
return component.callMe(); return component.get();
} }
/** /**
@ -54,6 +54,18 @@ public class DemoService {
return BlackBox.secretBox(); return BlackBox.secretBox();
} }
/**
* Target 7 - method with override method invoke
*/
public Box putBox() {
Box box = new BlackBox("");
box.put("data");
return box;
}
/**
* Target 8 - two methods invoke same private method
*/
public String callerOne() { public String callerOne() {
return callFromDifferentMethod(); return callFromDifferentMethod();
} }

View File

@ -43,6 +43,11 @@ class DemoServiceTest {
return new BlackBox("not_secret_box"); return new BlackBox("not_secret_box");
} }
@TestableMock
private void put(Box self, String something) {
self.put("put_" + something + "_mocked");
}
@TestableMock @TestableMock
private String callFromDifferentMethod(DemoService self) { private String callFromDifferentMethod(DemoService self) {
if (TEST_CASE.equals("should_able_to_get_test_case_name")) { if (TEST_CASE.equals("should_able_to_get_test_case_name")) {
@ -93,10 +98,17 @@ class DemoServiceTest {
@Test @Test
void should_able_to_mock_static_method() throws Exception { void should_able_to_mock_static_method() throws Exception {
assertEquals("not_secret_box", demoService.getBox().callMe()); assertEquals("not_secret_box", demoService.getBox().get());
verify("secretBox").times(1); verify("secretBox").times(1);
} }
@Test
void should_able_to_mock_override_method() throws Exception {
BlackBox box = (BlackBox)demoService.putBox();
verify("put").times(1);
assertEquals("put_data_mocked", box.get());
}
@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

@ -1,9 +1,13 @@
package com.alibaba.testable.demo package com.alibaba.testable.demo
class BlackBox(private val data: String) { class BlackBox(private var data: String) : Box {
fun callMe(): String { override fun put(something: String) {
data = something
}
fun get(): String {
return data return data
} }
@ -29,6 +33,6 @@ class BlackBox(private val data: String) {
object ColorBox { object ColorBox {
fun createBox(color: String, box: BlackBox): BlackBox { fun createBox(color: String, box: BlackBox): BlackBox {
return BlackBox("${color}_${box.callMe()}") return BlackBox("${color}_${box.get()}")
} }
} }

View File

@ -0,0 +1,7 @@
package com.alibaba.testable.demo
interface Box {
fun put(something: String)
}

View File

@ -29,7 +29,7 @@ class DemoService {
* Target 3 - method with new operation * Target 3 - method with new operation
*/ */
fun newFunc(): String { fun newFunc(): String {
return BlackBox("something").callMe() return BlackBox("something").get()
} }
/** /**
@ -54,6 +54,18 @@ class DemoService {
return ColorBox.createBox("Red", BlackBox.secretBox()) return ColorBox.createBox("Red", BlackBox.secretBox())
} }
/**
* Target 7 - method with override method invoke
*/
fun putBox(): Box {
val box: Box = BlackBox("")
box.put("data")
return box
}
/**
* Target 8 - two methods invoke same private method
*/
fun callerOne(): String { fun callerOne(): String {
return callFromDifferentMethod() return callFromDifferentMethod()
} }

View File

@ -34,7 +34,12 @@ internal class DemoServiceTest {
@TestableMock @TestableMock
private fun createBox(ignore: ColorBox, color: String, box: BlackBox): BlackBox { private fun createBox(ignore: ColorBox, color: String, box: BlackBox): BlackBox {
return BlackBox("White_${box.callMe()}") return BlackBox("White_${box.get()}")
}
@TestableMock
private fun put(self: Box, something: String) {
self.put("put_" + something + "_mocked")
} }
@TestableMock @TestableMock
@ -85,11 +90,18 @@ internal class DemoServiceTest {
@Test @Test
fun should_able_to_mock_static_method() { fun should_able_to_mock_static_method() {
assertEquals("White_not_secret_box", demoService.getBox().callMe()) assertEquals("White_not_secret_box", demoService.getBox().get())
verify("secretBox").times(1) verify("secretBox").times(1)
verify("createBox").times(1) verify("createBox").times(1)
} }
@Test
fun should_able_to_mock_override_method() {
val box = demoService.putBox() as BlackBox
verify("put").times(1)
assertEquals("put_data_mocked", box.get())
}
@Test @Test
fun should_able_to_get_source_method_name() { fun should_able_to_get_source_method_name() {
// synchronous // synchronous

View File

@ -19,9 +19,10 @@ 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.INVOKEVIRTUAL); add(Opcodes.INVOKEVIRTUAL);
add(Opcodes.INVOKESPECIAL);
add(Opcodes.INVOKESTATIC);
add(Opcodes.INVOKEINTERFACE);
}}; }};
public SourceClassHandler(List<MethodInfo> injectMethods) { public SourceClassHandler(List<MethodInfo> injectMethods) {
@ -58,7 +59,7 @@ public class SourceClassHandler extends BaseClassHandler {
MethodInsnNode node = (MethodInsnNode)instructions[i]; MethodInsnNode node = (MethodInsnNode)instructions[i];
String memberInjectMethodName = getMemberInjectMethodName(memberInjectMethodList, node); String memberInjectMethodName = getMemberInjectMethodName(memberInjectMethodList, node);
if (memberInjectMethodName != null) { if (memberInjectMethodName != null) {
// it's a member method and an inject method for it exist // it's a member or static 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, memberInjectMethodName, instructions, instructions = replaceMemberCallOps(cn, mn, memberInjectMethodName, instructions,
@ -119,11 +120,11 @@ public class SourceClassHandler extends BaseClassHandler {
int stackLevel = ClassUtil.getParameterTypes(((MethodInsnNode)instructions[rangeEnd]).desc).size(); int stackLevel = ClassUtil.getParameterTypes(((MethodInsnNode)instructions[rangeEnd]).desc).size();
for (int i = rangeEnd - 1; i >= 0; i--) { for (int i = rangeEnd - 1; i >= 0; i--) {
switch (instructions[i].getOpcode()) { switch (instructions[i].getOpcode()) {
case Opcodes.INVOKEINTERFACE:
case Opcodes.INVOKEVIRTUAL: case Opcodes.INVOKEVIRTUAL:
case Opcodes.INVOKESPECIAL: case Opcodes.INVOKESPECIAL:
case Opcodes.INVOKEDYNAMIC:
case Opcodes.INVOKESTATIC: case Opcodes.INVOKESTATIC:
case Opcodes.INVOKEINTERFACE:
case Opcodes.INVOKEDYNAMIC:
stackLevel += ClassUtil.getParameterTypes(((MethodInsnNode)instructions[i]).desc).size(); stackLevel += ClassUtil.getParameterTypes(((MethodInsnNode)instructions[i]).desc).size();
break; break;
case -1: case -1: