diff --git a/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/BlackBox.java b/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/BlackBox.java
index 699a09c..2071107 100644
--- a/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/BlackBox.java
+++ b/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/BlackBox.java
@@ -1,24 +1,23 @@
package com.alibaba.testable.demo.model;
-public class BlackBox implements Box {
-
- private String data;
-
- @Override
- public void put(String something) {
- data = something;
- }
+public class BlackBox extends Box implements Color {
public BlackBox(String data) {
this.data = data;
}
- public String get() {
- return data;
- }
-
public static BlackBox secretBox() {
return new BlackBox("secret");
}
+ @Override
+ public void put(String something) {
+ data = something;
+ }
+
+ @Override
+ public String getColor() {
+ return "black";
+ }
+
}
diff --git a/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/Box.java b/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/Box.java
index 2778e72..a280f9d 100644
--- a/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/Box.java
+++ b/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/Box.java
@@ -1,7 +1,13 @@
package com.alibaba.testable.demo.model;
-public interface Box {
+abstract public class Box {
- void put(String something);
+ protected String data;
+
+ abstract public void put(String something);
+
+ public String get() {
+ return data;
+ }
}
diff --git a/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/Color.java b/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/Color.java
new file mode 100644
index 0000000..daa5ba2
--- /dev/null
+++ b/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/Color.java
@@ -0,0 +1,7 @@
+package com.alibaba.testable.demo.model;
+
+public interface Color {
+
+ String getColor();
+
+}
diff --git a/demo/java-demo/src/main/java/com/alibaba/testable/demo/service/DemoInheritService.java b/demo/java-demo/src/main/java/com/alibaba/testable/demo/service/DemoInheritService.java
new file mode 100644
index 0000000..fdcb246
--- /dev/null
+++ b/demo/java-demo/src/main/java/com/alibaba/testable/demo/service/DemoInheritService.java
@@ -0,0 +1,60 @@
+package com.alibaba.testable.demo.service;
+
+import com.alibaba.testable.demo.model.BlackBox;
+import com.alibaba.testable.demo.model.Box;
+import com.alibaba.testable.demo.model.Color;
+import org.springframework.stereotype.Service;
+
+@Service
+public class DemoInheritService {
+
+ /**
+ * call method overridden by sub class via parent class variable
+ */
+ public Box putIntoBox() {
+ Box box = new BlackBox("");
+ box.put("data");
+ return box;
+ }
+
+ /**
+ * call method overridden by sub class via sub class variable
+ */
+ public BlackBox putIntoBlackBox() {
+ BlackBox box = new BlackBox("");
+ box.put("data");
+ return box;
+ }
+
+ /**
+ * call method defined in parent class via parent class variable
+ */
+ public String getFromBox() {
+ Box box = new BlackBox("data");
+ return box.get();
+ }
+
+ /**
+ * call method defined in parent class via sub class variable
+ */
+ public String getFromBlackBox() {
+ BlackBox box = new BlackBox("data");
+ return box.get();
+ }
+
+ /**
+ * call method defined in interface via interface variable
+ */
+ public String getColorViaColor() {
+ Color color = new BlackBox("");
+ return color.getColor();
+ }
+
+ /**
+ * call method defined in interface via sub class variable
+ */
+ public String getColorViaBox() {
+ BlackBox box = new BlackBox("");
+ return box.getColor();
+ }
+}
diff --git a/demo/java-demo/src/main/java/com/alibaba/testable/demo/service/DemoMockService.java b/demo/java-demo/src/main/java/com/alibaba/testable/demo/service/DemoMockService.java
index f7a619f..d7468ee 100644
--- a/demo/java-demo/src/main/java/com/alibaba/testable/demo/service/DemoMockService.java
+++ b/demo/java-demo/src/main/java/com/alibaba/testable/demo/service/DemoMockService.java
@@ -39,15 +39,6 @@ public class DemoMockService {
return BlackBox.secretBox();
}
- /**
- * method with override method invoke
- */
- public Box putBox() {
- Box box = new BlackBox("");
- box.put("data");
- return box;
- }
-
/**
* two methods invoke same private method
*/
diff --git a/demo/java-demo/src/test/java/com/alibaba/testable/demo/service/DemoInheritServiceTest.java b/demo/java-demo/src/test/java/com/alibaba/testable/demo/service/DemoInheritServiceTest.java
new file mode 100644
index 0000000..6f56b56
--- /dev/null
+++ b/demo/java-demo/src/test/java/com/alibaba/testable/demo/service/DemoInheritServiceTest.java
@@ -0,0 +1,88 @@
+package com.alibaba.testable.demo.service;
+
+import com.alibaba.testable.core.annotation.TestableMock;
+import com.alibaba.testable.demo.model.BlackBox;
+import com.alibaba.testable.demo.model.Box;
+import com.alibaba.testable.demo.model.Color;
+import org.junit.jupiter.api.Test;
+
+import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
+import static org.junit.jupiter.api.Assertions.*;
+
+class DemoInheritServiceTest {
+
+ @TestableMock(targetMethod = "put")
+ private void put_into_box(Box self, String something) {
+ self.put("put_" + something + "_into_box");
+ }
+
+ @TestableMock(targetMethod = "put")
+ private void put_into_blackbox(BlackBox self, String something) {
+ self.put("put_" + something + "_into_blackbox");
+ }
+
+ @TestableMock(targetMethod = "get")
+ private String get_from_box(Box self) {
+ return "get_from_box";
+ }
+
+ @TestableMock(targetMethod = "get")
+ private String get_from_blackbox(BlackBox self) {
+ return "get_from_blackbox";
+ }
+
+ @TestableMock(targetMethod = "getColor")
+ private String get_color_from_color(Color self) {
+ return "color_from_color";
+ }
+
+ @TestableMock(targetMethod = "getColor")
+ private String get_color_from_blackbox(BlackBox self) {
+ return "color_from_blackbox";
+ }
+
+ private DemoInheritService inheritService = new DemoInheritService();
+
+ @Test
+ void should_able_to_mock_call_sub_object_method_by_parent_object() throws Exception {
+ BlackBox box = (BlackBox)inheritService.putIntoBox();
+ verify("put_into_box").withTimes(1);
+ assertEquals("put_data_into_box", box.get());
+ }
+
+ @Test
+ void should_able_to_mock_call_sub_object_method_by_sub_object() throws Exception {
+ BlackBox box = inheritService.putIntoBlackBox();
+ verify("put_into_blackbox").withTimes(1);
+ assertEquals("put_data_into_blackbox", box.get());
+ }
+
+ @Test
+ void should_able_to_mock_call_parent_object_method_by_parent_object() throws Exception {
+ String content = inheritService.getFromBox();
+ verify("get_from_box").withTimes(1);
+ assertEquals("get_from_box", content);
+ }
+
+ @Test
+ void should_able_to_mock_call_parent_object_method_by_sub_object() throws Exception {
+ String content = inheritService.getFromBlackBox();
+ verify("get_from_blackbox").withTimes(1);
+ assertEquals("get_from_blackbox", content);
+ }
+
+ @Test
+ void should_able_to_mock_call_interface_method_by_interface_object() throws Exception {
+ String color = inheritService.getColorViaColor();
+ verify("get_color_from_color").withTimes(1);
+ assertEquals("color_from_color", color);
+ }
+
+ @Test
+ void should_able_to_mock_call_interface_method_by_sub_class_object() throws Exception {
+ String color = inheritService.getColorViaBox();
+ verify("get_color_from_blackbox").withTimes(1);
+ assertEquals("color_from_blackbox", color);
+ }
+
+}
diff --git a/demo/java-demo/src/test/java/com/alibaba/testable/demo/service/DemoMockServiceTest.java b/demo/java-demo/src/test/java/com/alibaba/testable/demo/service/DemoMockServiceTest.java
index 431338b..7590ebd 100644
--- a/demo/java-demo/src/test/java/com/alibaba/testable/demo/service/DemoMockServiceTest.java
+++ b/demo/java-demo/src/test/java/com/alibaba/testable/demo/service/DemoMockServiceTest.java
@@ -2,7 +2,6 @@ package com.alibaba.testable.demo.service;
import com.alibaba.testable.core.annotation.TestableMock;
import com.alibaba.testable.demo.model.BlackBox;
-import com.alibaba.testable.demo.model.Box;
import org.junit.jupiter.api.Test;
import java.util.concurrent.Executors;
@@ -43,11 +42,6 @@ class DemoMockServiceTest {
return new BlackBox("not_secret_box");
}
- @TestableMock
- private void put(Box self, String something) {
- self.put("put_" + something + "_mocked");
- }
-
@TestableMock
private String callFromDifferentMethod(DemoMockService self) {
if (TEST_CASE.equals("should_able_to_get_test_case_name")) {
@@ -87,13 +81,6 @@ class DemoMockServiceTest {
verify("secretBox").withTimes(1);
}
- @Test
- void should_able_to_mock_override_method() throws Exception {
- BlackBox box = (BlackBox)demoService.putBox();
- verify("put").withTimes(1);
- assertEquals("put_data_mocked", box.get());
- }
-
@Test
void should_able_to_get_source_method_name() throws Exception {
// synchronous
diff --git a/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/BlackBox.kt b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/BlackBox.kt
index e6be1be..c8dec9b 100644
--- a/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/BlackBox.kt
+++ b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/BlackBox.kt
@@ -1,26 +1,29 @@
package com.alibaba.testable.demo.model
-class BlackBox(private var data: String) : Box {
+class BlackBox(var input: String) : Box(), Color {
+
+ init {
+ this.content = input
+ }
override fun put(something: String) {
- data = something
+ content = something
}
- fun get(): String {
- return data
+ override val color: String
+ get() = "black"
+
+ fun trim(): String? {
+ return content?.trim()
}
- fun trim(): String {
- return data.trim()
- }
-
- fun substring(from: Int, to: Int): String {
- return data.substring(from, to)
+ fun substring(from: Int, to: Int): String? {
+ return content?.substring(from, to)
}
fun startsWith(prefix: String): Boolean {
- return data.startsWith(prefix)
+ return content?.startsWith(prefix) == true
}
companion object {
diff --git a/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/Box.kt b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/Box.kt
index d88da88..e7ccd2b 100644
--- a/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/Box.kt
+++ b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/Box.kt
@@ -1,7 +1,13 @@
package com.alibaba.testable.demo.model
-interface Box {
+abstract class Box {
- fun put(something: String)
+ var content: String? = null
+
+ abstract fun put(something: String)
+
+ open fun get(): String? {
+ return content
+ }
}
diff --git a/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/Color.kt b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/Color.kt
new file mode 100644
index 0000000..ab52230
--- /dev/null
+++ b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/Color.kt
@@ -0,0 +1,7 @@
+package com.alibaba.testable.demo.model
+
+interface Color {
+
+ val color: String
+
+}
diff --git a/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/service/DemoInheritService.kt b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/service/DemoInheritService.kt
new file mode 100644
index 0000000..64e6ae4
--- /dev/null
+++ b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/service/DemoInheritService.kt
@@ -0,0 +1,64 @@
+package com.alibaba.testable.demo.service
+
+import com.alibaba.testable.demo.model.BlackBox
+import com.alibaba.testable.demo.model.Box
+import com.alibaba.testable.demo.model.Color
+import org.springframework.stereotype.Service
+
+@Service
+class DemoInheritService {
+
+ /**
+ * call method overridden by sub class via parent class variable
+ */
+ fun putIntoBox(): Box {
+ val box: Box = BlackBox("")
+ box.put("data")
+ return box
+ }
+
+ /**
+ * call method overridden by sub class via sub class variable
+ */
+ fun putIntoBlackBox(): BlackBox {
+ val box = BlackBox("")
+ box.put("data")
+ return box
+ }
+
+ /**
+ * call method defined in parent class via parent class variable
+ */
+ val fromBox: String?
+ get() {
+ val box: Box = BlackBox("data")
+ return box.get()
+ }
+
+ /**
+ * call method defined in parent class via sub class variable
+ */
+ val fromBlackBox: String?
+ get() {
+ val box = BlackBox("data")
+ return box.get()
+ }
+
+ /**
+ * call method defined in interface via interface variable
+ */
+ val colorViaColor: String
+ get() {
+ val color: Color = BlackBox("")
+ return color.color
+ }
+
+ /**
+ * call method defined in interface via sub class variable
+ */
+ val colorViaBox: String
+ get() {
+ val box = BlackBox("")
+ return box.color
+ }
+}
diff --git a/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/service/DemoMockService.kt b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/service/DemoMockService.kt
index 845d568..a04ade1 100644
--- a/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/service/DemoMockService.kt
+++ b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/service/DemoMockService.kt
@@ -14,7 +14,7 @@ class DemoMockService {
/**
* method with new operation
*/
- fun newFunc(): String {
+ fun newFunc(): String? {
return BlackBox("something").get()
}
@@ -40,15 +40,6 @@ class DemoMockService {
return ColorBox.createBox("Red", BlackBox.secretBox())
}
- /**
- * method with override method invoke
- */
- fun putBox(): Box {
- val box: Box = BlackBox("")
- box.put("data")
- return box
- }
-
/**
* two methods invoke same private method
*/
diff --git a/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/service/DemoInheritServiceTest.kt b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/service/DemoInheritServiceTest.kt
new file mode 100644
index 0000000..67e1d24
--- /dev/null
+++ b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/service/DemoInheritServiceTest.kt
@@ -0,0 +1,86 @@
+package com.alibaba.testable.demo.service
+
+import com.alibaba.testable.core.annotation.TestableMock
+import com.alibaba.testable.core.matcher.InvokeVerifier
+import com.alibaba.testable.demo.model.BlackBox
+import com.alibaba.testable.demo.model.Box
+import com.alibaba.testable.demo.model.Color
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Test
+
+internal class DemoInheritServiceTest {
+
+ @TestableMock(targetMethod = "put")
+ private fun put_into_box(self: Box, something: String) {
+ self.put("put_" + something + "_into_box")
+ }
+
+ @TestableMock(targetMethod = "put")
+ private fun put_into_blackbox(self: BlackBox, something: String) {
+ self.put("put_" + something + "_into_blackbox")
+ }
+
+ @TestableMock(targetMethod = "get")
+ private fun get_from_box(self: Box): String {
+ return "get_from_box"
+ }
+
+ @TestableMock(targetMethod = "get")
+ private fun get_from_blackbox(self: BlackBox): String {
+ return "get_from_blackbox"
+ }
+
+ @TestableMock(targetMethod = "getColor")
+ private fun get_color_from_color(self: Color): String {
+ return "color_from_color"
+ }
+
+ @TestableMock(targetMethod = "getColor")
+ private fun get_color_from_blackbox(self: BlackBox): String {
+ return "color_from_blackbox"
+ }
+
+ private val inheritService = DemoInheritService()
+
+ @Test
+ fun should_able_to_mock_call_sub_object_method_by_parent_object() {
+ val box = inheritService.putIntoBox() as BlackBox
+ InvokeVerifier.verify("put_into_box").withTimes(1)
+ Assertions.assertEquals("put_data_into_box", box.get())
+ }
+
+ @Test
+ fun should_able_to_mock_call_sub_object_method_by_sub_object() {
+ val box = inheritService.putIntoBlackBox()
+ InvokeVerifier.verify("put_into_blackbox").withTimes(1)
+ Assertions.assertEquals("put_data_into_blackbox", box.get())
+ }
+
+ @Test
+ fun should_able_to_mock_call_parent_object_method_by_parent_object() {
+ val content = inheritService.fromBox
+ InvokeVerifier.verify("get_from_box").withTimes(1)
+ Assertions.assertEquals("get_from_box", content)
+ }
+
+ @Test
+ fun should_able_to_mock_call_parent_object_method_by_sub_object() {
+ val content = inheritService.fromBlackBox
+ InvokeVerifier.verify("get_from_blackbox").withTimes(1)
+ Assertions.assertEquals("get_from_blackbox", content)
+ }
+
+ @Test
+ fun should_able_to_mock_call_interface_method_by_interface_object() {
+ val color = inheritService.colorViaColor
+ InvokeVerifier.verify("get_color_from_color").withTimes(1)
+ Assertions.assertEquals("color_from_color", color)
+ }
+
+ @Test
+ fun should_able_to_mock_call_interface_method_by_sub_class_object() {
+ val color = inheritService.colorViaBox
+ InvokeVerifier.verify("get_color_from_blackbox").withTimes(1)
+ Assertions.assertEquals("color_from_blackbox", color)
+ }
+}
diff --git a/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/service/DemoMockServiceTest.kt b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/service/DemoMockServiceTest.kt
index 936684c..898f4a5 100644
--- a/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/service/DemoMockServiceTest.kt
+++ b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/service/DemoMockServiceTest.kt
@@ -4,7 +4,6 @@ import com.alibaba.testable.core.annotation.TestableMock
import com.alibaba.testable.core.matcher.InvokeVerifier.verify
import com.alibaba.testable.core.tool.TestableTool.*
import com.alibaba.testable.demo.model.BlackBox
-import com.alibaba.testable.demo.model.Box
import com.alibaba.testable.demo.model.ColorBox
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
@@ -37,11 +36,6 @@ internal class DemoMockServiceTest {
return BlackBox("White_${box.get()}")
}
- @TestableMock
- private fun put(self: Box, something: String) {
- self.put("put_" + something + "_mocked")
- }
-
@TestableMock
private fun callFromDifferentMethod(self: DemoMockService): String {
return if (TEST_CASE == "should_able_to_get_test_case_name") {
@@ -83,13 +77,6 @@ internal class DemoMockServiceTest {
verify("createBox").withTimes(1)
}
- @Test
- fun should_able_to_mock_override_method() {
- val box = demoService.putBox() as BlackBox
- verify("put").withTimes(1)
- assertEquals("put_data_mocked", box.get())
- }
-
@Test
fun should_able_to_get_source_method_name() {
// synchronous
diff --git a/docs/frequency-asked-questions.md b/docs/frequency-asked-questions.md
index adcec88..be7e6c8 100644
--- a/docs/frequency-asked-questions.md
+++ b/docs/frequency-asked-questions.md
@@ -5,6 +5,18 @@
直接创建被测类对象,然后利用`TestableMock`访问私有成员的能力直接给这些字段赋值即可。
-#### 2. 通过接口对象或基类对象指向派生类的实例,调用执行了派生类实现的方法。使用`@TestableMock`定义Mock方法时,首个参数类型应该用 接口/基类 还是 派生类?
+#### 2. 父类变量指向子类对象时,如何实现Mock方法?
-应该使用 接口/基类 类型,参见`should_able_to_mock_override_method`测试用例。
+在代码中,经常会有使用接口变量或父类变量指向子类实例,调用父类或子类方法的情况。
+
+这时候遵循一个原则,Mock方法的首个参数类型**始终与发起调用的变量类型一致**。
+
+因此,不论被调用方法来自父类还是子类,也不论子类是否覆写该方法,Mock方法的首个参数类型都应该使用变量自身的接口或父类类型。
+
+参见Java和Kotlin示例中`DemoInheritServiceTest`测试类的用例。
+
+#### 3. `TestableMock`能否用于Android项目的测试?
+
+结合[Roboelectric](https://github.com/robolectric/robolectric)测试框架可使用。
+
+Android系统的`Dalvik`和`ART`虚拟机采用了与标准JVM不同的字节码体系,会影响`TestableMock`的正常工作。`Roboelectric`框架能在普通JVM虚拟机上运行Android单元测试,其速度比通过Android虚拟机运行单元测试快非常多,当下绝大多数Android App的单元测试都使用了`Roboelectric`框架。