mirror of
https://github.com/alibaba/testable-mock.git
synced 2026-08-30 03:08:21 +08:00
Compare commits
219 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b41923bfa0 | ||
|
|
7c1f18de46 | ||
|
|
c393d40d0d | ||
|
|
d4a69528a7 | ||
|
|
a46e73a421 | ||
|
|
01e9890234 | ||
|
|
93cdef1d6c | ||
|
|
e2e986e50d | ||
|
|
f5475aa18e | ||
|
|
844323af3a | ||
|
|
cc2deeff1c | ||
|
|
14e4d2db5b | ||
|
|
c9c8ba6c24 | ||
|
|
b5c0afb3da | ||
|
|
617d372545 | ||
|
|
58da25de53 | ||
|
|
46706ba56c | ||
|
|
1ced04bf29 | ||
|
|
7b2f967626 | ||
|
|
e0a4a39d55 | ||
|
|
b4da23a90e | ||
|
|
c1659242b6 | ||
|
|
929abffb66 | ||
|
|
e370114b01 | ||
|
|
26d3178840 | ||
|
|
55ccc385d5 | ||
|
|
20b346ed34 | ||
|
|
00055a4626 | ||
|
|
27174afa93 | ||
|
|
3d6d3c9dd1 | ||
|
|
df7e641a0c | ||
|
|
3acf341d1f | ||
|
|
52878364ee | ||
|
|
9871a96db0 | ||
|
|
9b56e3d64a | ||
|
|
ea8f305152 | ||
|
|
3feba7fdfa | ||
|
|
9064f7a582 | ||
|
|
b062dbc592 | ||
|
|
a623bd0a33 | ||
|
|
a54bb33c09 | ||
|
|
bbc41f2987 | ||
|
|
7b9a0964bc | ||
|
|
a0dae0dfe1 | ||
|
|
f54a6ce022 | ||
|
|
606760d632 | ||
|
|
206fc5486e | ||
|
|
af3af6b272 | ||
|
|
fe7420c99a | ||
|
|
7857782125 | ||
|
|
f776deff0e | ||
|
|
328296ae3a | ||
|
|
a5038320b4 | ||
|
|
bdd16c0948 | ||
|
|
dce1accafb | ||
|
|
2865093fef | ||
|
|
5f69b21532 | ||
|
|
250a1beaf9 | ||
|
|
323ab398b2 | ||
|
|
edd4c24d88 | ||
|
|
4995bbd9a9 | ||
|
|
121536a337 | ||
|
|
b163152c2f | ||
|
|
5897e087c8 | ||
|
|
6f079db3f3 | ||
|
|
40073ad784 | ||
|
|
e46ec3d98a | ||
|
|
1849cf85c5 | ||
|
|
f8450d7047 | ||
|
|
196dffc73d | ||
|
|
1ff6ec5961 | ||
|
|
e50633b884 | ||
|
|
2976ca0211 | ||
|
|
24c6a9cc5c | ||
|
|
9e7ceb2dc1 | ||
|
|
8dfd0b4516 | ||
|
|
190cf4cc3c | ||
|
|
d49f526376 | ||
|
|
f01ff10fba | ||
|
|
f64b07e9da | ||
|
|
f96d272f97 | ||
|
|
b1e6ad58cc | ||
|
|
48d6ef328b | ||
|
|
84df2a0fd8 | ||
|
|
f9621a6f2e | ||
|
|
55ccabbb39 | ||
|
|
cfc28467bb | ||
|
|
23a0a0cd90 | ||
|
|
a1086ff402 | ||
|
|
6237e19a32 | ||
|
|
df73dac54f | ||
|
|
f660ccb88f | ||
|
|
82d87f3447 | ||
|
|
65cc631d39 | ||
|
|
bdd99577c4 | ||
|
|
8be5550331 | ||
|
|
6a5c817a3a | ||
|
|
c76ed2d84a | ||
|
|
14549040d9 | ||
|
|
03ec857cd0 | ||
|
|
b4a29e7e8e | ||
|
|
02c74299f4 | ||
|
|
4c6fc5b228 | ||
|
|
6d6e2ecb7f | ||
|
|
c364e96f77 | ||
|
|
dab1d36a81 | ||
|
|
01bd676df7 | ||
|
|
02d7fb6e91 | ||
|
|
d8ffcacdaf | ||
|
|
be53ea2d9c | ||
|
|
9eb8682ec0 | ||
|
|
2a54fe70dd | ||
|
|
790409a336 | ||
|
|
189ef43565 | ||
|
|
34f1dc8f73 | ||
|
|
b60ebf2f2c | ||
|
|
d95cba6d37 | ||
|
|
5ef06c4bde | ||
|
|
8814276d63 | ||
|
|
374a4d5442 | ||
|
|
9a22f361f3 | ||
|
|
94c5aa8621 | ||
|
|
74e3cf16a4 | ||
|
|
19f28c32db | ||
|
|
affa56d057 | ||
|
|
f1edf92626 | ||
|
|
6f9accf319 | ||
|
|
cefc5fb1df | ||
|
|
948878687c | ||
|
|
d626906d28 | ||
|
|
705ba796f1 | ||
|
|
7e44a66205 | ||
|
|
d57dedb6fc | ||
|
|
d860684e91 | ||
|
|
7bbdf82a41 | ||
|
|
126c7257d8 | ||
|
|
adfba4bac0 | ||
|
|
2c18ea12ed | ||
|
|
d4bce13918 | ||
|
|
b23b8b98b3 | ||
|
|
f228ed5002 | ||
|
|
2303ac188e | ||
|
|
290d0db400 | ||
|
|
77b955a5ce | ||
|
|
ab8a0b32e5 | ||
|
|
39c788cd02 | ||
|
|
7029579534 | ||
|
|
ba951ce744 | ||
|
|
2bbe2eba93 | ||
|
|
4c96f4cc0e | ||
|
|
3a2e3032df | ||
|
|
530f3295bd | ||
|
|
650b385785 | ||
|
|
99e7c3bf18 | ||
|
|
ef5d21baf6 | ||
|
|
f9be92e9d0 | ||
|
|
019e407a4f | ||
|
|
6951b98c7e | ||
|
|
369c2bd192 | ||
|
|
13de5e42ad | ||
|
|
453908cd46 | ||
|
|
2f110ae4d6 | ||
|
|
7ba44ae618 | ||
|
|
66cb8f7732 | ||
|
|
1f29aff00f | ||
|
|
8d12bb42ba | ||
|
|
c1744d2254 | ||
|
|
444967f676 | ||
|
|
860fdcf072 | ||
|
|
851ae91406 | ||
|
|
580fed7dac | ||
|
|
f07ac8f21f | ||
|
|
31a0b49b7a | ||
|
|
0a1b46b352 | ||
|
|
55ec1ca5b5 | ||
|
|
2806d38c94 | ||
|
|
070aeb6559 | ||
|
|
0255ffa911 | ||
|
|
f64e0cd959 | ||
|
|
74b68d495a | ||
|
|
6c0bd7de46 | ||
|
|
593d134672 | ||
|
|
c496837861 | ||
|
|
b42ee1c9b9 | ||
|
|
079410ee0f | ||
|
|
acc3f0bdf2 | ||
|
|
6f9d535cee | ||
|
|
631f45f4c5 | ||
|
|
94127064e8 | ||
|
|
36f170bdd3 | ||
|
|
5ab1cf7a40 | ||
|
|
68075a2ead | ||
|
|
783b127bbb | ||
|
|
cd7097ded9 | ||
|
|
0f2c889c46 | ||
|
|
df4ac8e950 | ||
|
|
9e393b5ef5 | ||
|
|
bb47f5d199 | ||
|
|
2efee1feb2 | ||
|
|
3df95db607 | ||
|
|
a6b289e136 | ||
|
|
3c375713aa | ||
|
|
9a7372d9ab | ||
|
|
e11f2e1645 | ||
|
|
d14c570c96 | ||
|
|
8a8065e179 | ||
|
|
28bb6454c8 | ||
|
|
13fa8a76ce | ||
|
|
cbdab5a246 | ||
|
|
d7945f52f3 | ||
|
|
96ea5680df | ||
|
|
ee21c8dcd8 | ||
|
|
281be6bd51 | ||
|
|
a8b58ffe17 | ||
|
|
8d33cb718f | ||
|
|
2d8433056c | ||
|
|
32eefc25fe | ||
|
|
7f73fc0cc1 | ||
|
|
44fed6ce8b |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
# maven ignore
|
||||
target/
|
||||
.mvn/
|
||||
|
||||
# gradle ignore
|
||||
build/
|
||||
|
||||
19
README.md
19
README.md
@@ -2,13 +2,27 @@
|
||||
|
||||
换种思路写Mock,让单元测试更简单。
|
||||
|
||||
无需初始化,不挑测试框架,甭管要换的是私有方法、静态方法、构造方法还是其他任何类的任何方法,也甭管要换的对象是怎么创建的。写好Mock定义,加个`@MockMethod`注解,一切统统搞定。
|
||||
无需初始化,不挑服务框架,甭管要换的是私有方法、静态方法、构造方法还是其他任何类的任何方法,也甭管要换的对象是怎么创建的。写好Mock定义,加个`@MockMethod`注解,一切统统搞定。
|
||||
|
||||
- 文档:https://alibaba.github.io/testable-mock/
|
||||
- 国内文档镜像:http://freyrlin.gitee.io/testable-mock/ (速度快,内容稍有延迟)
|
||||
- 国内文档镜像:http://freyrlin.gitee.io/testable-mock/
|
||||
|
||||
阅读[这里](https://mp.weixin.qq.com/s/KyU6Eu7mDkZU8FspfSqfMw)了解更多故事。
|
||||
|
||||
<font size="5">**0.6版本已发布**</font>,从`0.5.x`升级到`0.6.x`版本请参考[版本升级指南](https://alibaba.github.io/testable-mock/#/zh-cn/doc/upgrade-guide)
|
||||
|
||||
如果有遇到其他任何使用问题和建议,请直接在[Issues](https://github.com/alibaba/testable-mock/issues)中提出,也可通过[Pull Request](https://github.com/alibaba/testable-mock/pulls)提交您的代码,我们将在24小时内回复并处理
|
||||
|
||||
-----
|
||||
|
||||
## 版本计划
|
||||
|
||||
`TestableMock`正在持续迭代演进,以下版本计划可能在开发过程中发生调整
|
||||
|
||||
- `0.6` 当前版本,进行中的工作内容参考[Issue](https://github.com/alibaba/testable-mock/issues)清单
|
||||
- `0.7` 完善Mock功能的防代码重构机制,支持快速Mock指定类型的所有方法
|
||||
- `1.0` 功能稳定,一个崭新的开始
|
||||
|
||||
## 目录结构
|
||||
|
||||
```bash
|
||||
@@ -18,6 +32,7 @@
|
||||
|-- testable-agent ➜ JavaAgent模块,提供Mock测试相关功能
|
||||
|-- testable-core ➜ 基础功能模块,提供Mock相关注解和工具类
|
||||
|-- testable-maven-plugin ➜ Maven插件模块,用于简化JavaAgent注入
|
||||
|-- tool ➜ 项目开发过程中的工具脚本
|
||||
|-- demo
|
||||
| |-- java-demo ➜ Java语言的示例代码
|
||||
| `-- kotlin-demo ➜ Kotlin语言的示例代码
|
||||
|
||||
11
README_EN.md
11
README_EN.md
@@ -7,6 +7,14 @@ Write a mock method, add an `@MockMethod` annotation, everything is done.
|
||||
|
||||
Usage Document: https://alibaba.github.io/testable-mock/#/en-us/
|
||||
|
||||
## Roadmap
|
||||
|
||||
`TestableMock` is still under heavy development, the following version plans may be adjusted during the iteration
|
||||
|
||||
- `v0.6` it's the current version, refer to the [issue](https://github.com/alibaba/testable-mock/issues) list for the work in progress
|
||||
- `v0.7` better anti-refactor support of mocking, allow quick mock all method in specified class
|
||||
- `v1.0` all functions are stable, a brand-new start
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```bash
|
||||
@@ -14,8 +22,9 @@ Usage Document: https://alibaba.github.io/testable-mock/#/en-us/
|
||||
|-- testable-all ➜ Dependence aggration, for easily import all modules at once
|
||||
|-- testable-processor ➜ Compile-time code preprocessing module, provides test assist functions
|
||||
|-- testable-agent ➜ JavaAgent module, provides test mocking related functions
|
||||
|-- testable-core ➜ Basic function module, provides Mock related class and annotation
|
||||
|-- testable-core ➜ Basic function module, provides mock related class and annotation
|
||||
|-- testable-maven-plugin ➜ Maven plugin module, for simplify JavaAgent injection
|
||||
|-- tool ➜ Scripts for project maintain
|
||||
|-- demo
|
||||
| |-- java-demo ➜ Java code example
|
||||
| `-- kotlin-demo ➜ Kotlin code example
|
||||
|
||||
@@ -7,13 +7,18 @@ version = '1.0.0-SNAPSHOT'
|
||||
sourceCompatibility = '8'
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
|
||||
testImplementation('com.alibaba.testable:testable-all:0.4.4')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.4.4')
|
||||
testImplementation('com.alibaba.testable:testable-all:0.6.1')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.6.1')
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
test {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<junit.version>5.6.2</junit.version>
|
||||
<testable.version>0.4.4</testable.version>
|
||||
<testable.version>0.6.1</testable.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -32,15 +32,14 @@
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0-M5</version>
|
||||
<configuration>
|
||||
<argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<configuration>
|
||||
<argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- <plugin>-->
|
||||
<!-- <groupId>com.alibaba.testable</groupId>-->
|
||||
<!-- <artifactId>testable-maven-plugin</artifactId>-->
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.alibaba.testable.demo;
|
||||
package com.alibaba.demo.basic;
|
||||
|
||||
import com.alibaba.testable.demo.model.BlackBox;
|
||||
import com.alibaba.testable.demo.model.Box;
|
||||
import com.alibaba.testable.demo.model.Color;
|
||||
import com.alibaba.demo.basic.model.mock.BlackBox;
|
||||
import com.alibaba.demo.basic.model.mock.Box;
|
||||
import com.alibaba.demo.basic.model.mock.Color;
|
||||
|
||||
/**
|
||||
* 演示父类变量引用子类对象时的Mock场景
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.alibaba.demo.basic;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* 演示对内部类的Mock支持
|
||||
* Demonstrate support for mocking invocation inside a inner class
|
||||
*/
|
||||
public class DemoInnerClass {
|
||||
|
||||
public static class StaticInner {
|
||||
/**
|
||||
* invocation inside a static inner class
|
||||
*/
|
||||
public String demo() {
|
||||
return methodToBeMock();
|
||||
}
|
||||
}
|
||||
|
||||
public class Inner {
|
||||
/**
|
||||
* invocation inside a non-static inner class
|
||||
*/
|
||||
public String demo() {
|
||||
return methodToBeMock();
|
||||
}
|
||||
}
|
||||
|
||||
private ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
|
||||
public String callAnonymousInner() throws ExecutionException, InterruptedException {
|
||||
/**
|
||||
* invocation inside a anonymous inner class
|
||||
*/
|
||||
Future<String> future = executorService.submit(new Callable<String>() {
|
||||
@Override
|
||||
public String call() throws Exception {
|
||||
return methodToBeMock();
|
||||
}
|
||||
});
|
||||
return future.get();
|
||||
}
|
||||
|
||||
public String callLambdaInner() throws ExecutionException, InterruptedException {
|
||||
/**
|
||||
* invocation inside a lambda inner class
|
||||
*/
|
||||
Future<String> future = executorService.submit(() -> methodToBeMock());
|
||||
return future.get();
|
||||
}
|
||||
|
||||
public String callInnerDemo() {
|
||||
return new Inner().demo();
|
||||
}
|
||||
|
||||
public static String methodToBeMock() {
|
||||
return "RealCall";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.alibaba.testable.demo;
|
||||
package com.alibaba.demo.basic;
|
||||
|
||||
|
||||
import com.alibaba.testable.demo.model.BlackBox;
|
||||
import com.alibaba.demo.basic.model.mock.BlackBox;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.alibaba.testable.demo;
|
||||
package com.alibaba.demo.basic;
|
||||
|
||||
import com.alibaba.testable.demo.model.BlackBox;
|
||||
import com.alibaba.demo.basic.model.mock.BlackBox;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.alibaba.demo.basic;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 演示私有成员访问功能
|
||||
* Demonstrate private member access functionality
|
||||
*/
|
||||
public class DemoPrivateAccess {
|
||||
|
||||
/**
|
||||
* a private static field
|
||||
*/
|
||||
private static int staticCount;
|
||||
/**
|
||||
* a private member field
|
||||
*/
|
||||
private int count;
|
||||
/**
|
||||
* a constant field
|
||||
*/
|
||||
public final Double pi = 3.14;
|
||||
|
||||
/**
|
||||
* private static method without arguments
|
||||
*/
|
||||
private static String privateStaticFunc() {
|
||||
return "static";
|
||||
}
|
||||
|
||||
/**
|
||||
* private static method with arguments
|
||||
*/
|
||||
private static String privateStaticFuncWithArgs(String str, int i) {
|
||||
return (str == null ? "null" : str) + " + " + i;
|
||||
}
|
||||
|
||||
/**
|
||||
* private member method without arguments
|
||||
*/
|
||||
private String privateFunc() {
|
||||
return "member";
|
||||
}
|
||||
|
||||
/**
|
||||
* private member method with arguments
|
||||
*/
|
||||
private String privateFuncWithArgs(List<String> list, String str, int i) {
|
||||
return list.stream().reduce((a, s) -> a + s).orElse("") + " + " + str + " + " + i;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.testable.demo;
|
||||
package com.alibaba.demo.basic;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.testable.demo.model;
|
||||
package com.alibaba.demo.basic.model.mock;
|
||||
|
||||
public class BlackBox extends Box implements Color {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.testable.demo.model;
|
||||
package com.alibaba.demo.basic.model.mock;
|
||||
|
||||
abstract public class Box {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.testable.demo.model;
|
||||
package com.alibaba.demo.basic.model.mock;
|
||||
|
||||
public interface Color {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.alibaba.demo.basic.model.omni;
|
||||
|
||||
public class Child {
|
||||
|
||||
/**
|
||||
* 我是一个私有的构造方法
|
||||
* This class have only private constructor
|
||||
*/
|
||||
private Child() {
|
||||
}
|
||||
|
||||
// ---------- 内部成员字段 ----------
|
||||
|
||||
private GrandChild grandChild;
|
||||
|
||||
private InnerChild subChild;
|
||||
|
||||
/* ---------------------------------------
|
||||
以下Getters/Setters方法仅为便于功能演示而添加
|
||||
并非OmniConstructor或OmniAccessor功能所需
|
||||
--------------------------------------- */
|
||||
|
||||
public GrandChild getGrandChild() {
|
||||
return grandChild;
|
||||
}
|
||||
|
||||
public void setGrandChild(GrandChild grandChild) {
|
||||
this.grandChild = grandChild;
|
||||
}
|
||||
|
||||
public InnerChild getSubChild() {
|
||||
return subChild;
|
||||
}
|
||||
|
||||
public void setSubChild(InnerChild subChild) {
|
||||
this.subChild = subChild;
|
||||
}
|
||||
|
||||
/**
|
||||
* 这是一个私有内部类
|
||||
* An private inner class
|
||||
*/
|
||||
private class InnerChild {
|
||||
private String secret;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.alibaba.demo.basic.model.omni;
|
||||
|
||||
public class GrandChild {
|
||||
|
||||
// ---------- 内部成员字段 ----------
|
||||
|
||||
private int value;
|
||||
|
||||
private String content;
|
||||
|
||||
/* ---------------------------------------
|
||||
以下Getters/Setters方法仅为便于功能演示而添加
|
||||
并非OmniConstructor或OmniAccessor功能所需
|
||||
--------------------------------------- */
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.alibaba.demo.basic.model.omni;
|
||||
|
||||
public class Parent {
|
||||
|
||||
/**
|
||||
* 我是一个虽然存在但无法正常使用的构造方法
|
||||
* This class have constructor with exception throw
|
||||
*/
|
||||
public Parent() {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
// ---------- 内部成员字段 ----------
|
||||
|
||||
private Child child;
|
||||
|
||||
private Child[] children;
|
||||
|
||||
/* ---------------------------------------
|
||||
以下Getters/Setters方法仅为便于功能演示而添加
|
||||
并非OmniConstructor或OmniAccessor功能所需
|
||||
--------------------------------------- */
|
||||
|
||||
public Child getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
public void setChild(Child child) {
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
public Child[] getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
public void setChildren(Child[] children) {
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.alibaba.demo.one2multi;
|
||||
|
||||
public class ASvc {
|
||||
|
||||
public String demo(String name) {
|
||||
return String.format("a_%s", name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.alibaba.demo.one2multi;
|
||||
|
||||
public class BSvc {
|
||||
|
||||
public String demo(String name) {
|
||||
return String.format("b_%s", name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.alibaba.demo.one2multi;
|
||||
|
||||
public class CSvc {
|
||||
|
||||
public String demo(String name) {
|
||||
return String.format("c_%s", name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.alibaba.testable.demo;
|
||||
|
||||
/**
|
||||
* 演示私有成员访问功能
|
||||
* Demonstrate private member access functionality
|
||||
*/
|
||||
public class DemoPrivateAccess {
|
||||
|
||||
/**
|
||||
* a private static field
|
||||
*/
|
||||
private static int staticCount;
|
||||
/**
|
||||
* a private member field
|
||||
*/
|
||||
private int count;
|
||||
/**
|
||||
* a constant field
|
||||
*/
|
||||
public final Double pi = 3.14;
|
||||
|
||||
/**
|
||||
* private static method
|
||||
*/
|
||||
private static String privateStaticFunc(String s, int i) {
|
||||
return s + " + " + i;
|
||||
}
|
||||
|
||||
/**
|
||||
* private member method
|
||||
*/
|
||||
private String privateFunc(String s, int i) {
|
||||
return s + " - " + i;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.alibaba.testable.demo;
|
||||
package com.alibaba.demo.basic;
|
||||
|
||||
import com.alibaba.demo.basic.model.mock.BlackBox;
|
||||
import com.alibaba.demo.basic.model.mock.Box;
|
||||
import com.alibaba.demo.basic.model.mock.Color;
|
||||
import com.alibaba.testable.core.annotation.MockMethod;
|
||||
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;
|
||||
@@ -17,74 +17,75 @@ class DemoInheritTest {
|
||||
|
||||
private DemoInherit demoInherit = new DemoInherit();
|
||||
|
||||
@MockMethod(targetMethod = "put")
|
||||
private void put_into_box(Box self, String something) {
|
||||
self.put("put_" + something + "_into_box");
|
||||
}
|
||||
public static class Mock {
|
||||
@MockMethod(targetMethod = "put")
|
||||
private void put_into_box(Box self, String something) {
|
||||
self.put("put_" + something + "_into_box");
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "put")
|
||||
private void put_into_blackbox(BlackBox self, String something) {
|
||||
self.put("put_" + something + "_into_blackbox");
|
||||
}
|
||||
@MockMethod(targetMethod = "put")
|
||||
private void put_into_blackbox(BlackBox self, String something) {
|
||||
self.put("put_" + something + "_into_blackbox");
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "get")
|
||||
private String get_from_box(Box self) {
|
||||
return "get_from_box";
|
||||
}
|
||||
@MockMethod(targetMethod = "get")
|
||||
private String get_from_box(Box self) {
|
||||
return "get_from_box";
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "get")
|
||||
private String get_from_blackbox(BlackBox self) {
|
||||
return "get_from_blackbox";
|
||||
}
|
||||
@MockMethod(targetMethod = "get")
|
||||
private String get_from_blackbox(BlackBox self) {
|
||||
return "get_from_blackbox";
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "getColor")
|
||||
private String get_color_from_color(Color self) {
|
||||
return "color_from_color";
|
||||
}
|
||||
@MockMethod(targetMethod = "getColor")
|
||||
private String get_color_from_color(Color self) {
|
||||
return "color_from_color";
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "getColor")
|
||||
private String get_color_from_blackbox(BlackBox self) {
|
||||
@MockMethod(targetMethod = "getColor")
|
||||
private String get_color_from_blackbox(BlackBox self) {
|
||||
return "color_from_blackbox";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_able_to_mock_call_sub_object_method_by_parent_object() {
|
||||
void should_mock_call_sub_object_method_by_parent_object() {
|
||||
BlackBox box = (BlackBox)demoInherit.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() {
|
||||
void should_mock_call_sub_object_method_by_sub_object() {
|
||||
BlackBox box = demoInherit.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() {
|
||||
void should_mock_call_parent_object_method_by_parent_object() {
|
||||
String content = demoInherit.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() {
|
||||
void should_mock_call_parent_object_method_by_sub_object() {
|
||||
String content = demoInherit.getFromBlackBox();
|
||||
verify("get_from_blackbox").withTimes(1);
|
||||
assertEquals("get_from_blackbox", content);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_able_to_mock_call_interface_method_by_interface_object() {
|
||||
void should_mock_call_interface_method_by_interface_object() {
|
||||
String color = demoInherit.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() {
|
||||
void should_mock_call_interface_method_by_sub_class_object() {
|
||||
String color = demoInherit.getColorViaBox();
|
||||
verify("get_color_from_blackbox").withTimes(1);
|
||||
assertEquals("color_from_blackbox", color);
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.alibaba.demo.basic;
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockMethod;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* 演示对内部类的Mock支持
|
||||
* Demonstrate support for mocking invocation inside a inner class
|
||||
*/
|
||||
class DemoInnerClassTest {
|
||||
|
||||
public static class Mock {
|
||||
@MockMethod(targetClass = DemoInnerClass.class)
|
||||
String methodToBeMock() {
|
||||
return "MockedCall";
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_mock_invoke_inside_inner_class() throws Exception {
|
||||
DemoInnerClass demo = new DemoInnerClass();
|
||||
assertEquals("MockedCall", demo.callInnerDemo());
|
||||
assertEquals("MockedCall", demo.callAnonymousInner());
|
||||
assertEquals("MockedCall", demo.callLambdaInner());
|
||||
assertEquals("MockedCall", new DemoInnerClass.StaticInner().demo());
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.alibaba.testable.demo;
|
||||
package com.alibaba.demo.basic;
|
||||
|
||||
import com.alibaba.demo.basic.model.mock.BlackBox;
|
||||
import com.alibaba.testable.core.annotation.MockMethod;
|
||||
import com.alibaba.testable.core.error.VerifyFailedError;
|
||||
import com.alibaba.testable.demo.model.BlackBox;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.alibaba.testable.core.matcher.InvokeMatcher.*;
|
||||
@@ -17,15 +17,16 @@ class DemoMatcherTest {
|
||||
|
||||
private DemoMatcher demoMatcher = new DemoMatcher();
|
||||
|
||||
@MockMethod(targetMethod = "methodToBeMocked")
|
||||
private void methodWithoutArgument(DemoMatcher self) {}
|
||||
public static class Mock {
|
||||
@MockMethod(targetMethod = "methodToBeMocked")
|
||||
private void methodWithoutArgument(DemoMatcher self) {}
|
||||
|
||||
@MockMethod(targetMethod = "methodToBeMocked")
|
||||
private void methodWithArguments(DemoMatcher self, Object a1, Object a2) {}
|
||||
|
||||
@MockMethod(targetMethod = "methodToBeMocked")
|
||||
private void methodWithArrayArgument(DemoMatcher self, Object[] a) {}
|
||||
@MockMethod(targetMethod = "methodToBeMocked")
|
||||
private void methodWithArguments(DemoMatcher self, Object a1, Object a2) {}
|
||||
|
||||
@MockMethod(targetMethod = "methodToBeMocked")
|
||||
private void methodWithArrayArgument(DemoMatcher self, Object[] a) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_match_no_argument() {
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.alibaba.demo.basic;
|
||||
|
||||
import com.alibaba.demo.basic.model.mock.BlackBox;
|
||||
import com.alibaba.testable.core.annotation.MockConstructor;
|
||||
import com.alibaba.testable.core.annotation.MockMethod;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
|
||||
import static com.alibaba.testable.core.tool.TestableTool.MOCK_CONTEXT;
|
||||
import static com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* 演示基本的Mock功能
|
||||
* Demonstrate basic mock functionality
|
||||
*/
|
||||
class DemoMockTest {
|
||||
|
||||
private DemoMock demoMock = new DemoMock();
|
||||
|
||||
public static class Mock {
|
||||
@MockConstructor
|
||||
private BlackBox createBlackBox(String text) {
|
||||
return new BlackBox("mock_" + text);
|
||||
}
|
||||
|
||||
@MockMethod(targetClass = DemoMock.class)
|
||||
private String innerFunc(String text) {
|
||||
return "mock_" + text;
|
||||
}
|
||||
|
||||
@MockMethod(targetClass = DemoMock.class)
|
||||
private String staticFunc() {
|
||||
return "_MOCK_TAIL";
|
||||
}
|
||||
|
||||
@MockMethod(targetClass = String.class)
|
||||
private String trim() {
|
||||
return "trim_string";
|
||||
}
|
||||
|
||||
@MockMethod(targetClass = String.class, targetMethod = "substring")
|
||||
private String sub(int i, int j) {
|
||||
return "sub_string";
|
||||
}
|
||||
|
||||
@MockMethod(targetClass = String.class)
|
||||
private boolean startsWith(String s) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@MockMethod(targetClass = BlackBox.class)
|
||||
private BlackBox secretBox() {
|
||||
return new BlackBox("not_secret_box");
|
||||
}
|
||||
|
||||
@MockMethod(targetClass = DemoMock.class)
|
||||
private String callFromDifferentMethod() {
|
||||
if ("special_case".equals(MOCK_CONTEXT.get("case"))) {
|
||||
return "mock_special";
|
||||
}
|
||||
switch (SOURCE_METHOD) {
|
||||
case "callerOne":
|
||||
return "mock_one";
|
||||
default:
|
||||
return "mock_others";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_mock_new_object() {
|
||||
assertEquals("mock_something", demoMock.newFunc());
|
||||
verify("createBlackBox").with("something");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_mock_member_method() throws Exception {
|
||||
assertEquals("{ \"res\": \"mock_hello_MOCK_TAIL\"}", demoMock.outerFunc("hello"));
|
||||
verify("innerFunc").with("hello");
|
||||
verify("staticFunc").with();
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_mock_common_method() {
|
||||
assertEquals("trim_string__sub_string__false", demoMock.commonFunc());
|
||||
verify("trim").withTimes(1);
|
||||
verify("sub").withTimes(1);
|
||||
verify("startsWith").withTimes(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_mock_static_method() {
|
||||
Assertions.assertEquals("not_secret_box", demoMock.getBox().get());
|
||||
verify("secretBox").withTimes(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_get_source_method_name() throws Exception {
|
||||
// synchronous
|
||||
assertEquals("mock_one_mock_others", demoMock.callerOne() + "_" + demoMock.callerTwo());
|
||||
// asynchronous
|
||||
assertEquals("mock_one_mock_others",
|
||||
Executors.newSingleThreadExecutor().submit(() -> demoMock.callerOne() + "_" + demoMock.callerTwo()).get());
|
||||
verify("callFromDifferentMethod").withTimes(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_set_mock_context() throws Exception {
|
||||
MOCK_CONTEXT.put("case", "special_case");
|
||||
// synchronous
|
||||
assertEquals("mock_special", demoMock.callerOne());
|
||||
// asynchronous
|
||||
assertEquals("mock_special", Executors.newSingleThreadExecutor().submit(() -> demoMock.callerOne()).get());
|
||||
verify("callFromDifferentMethod").withTimes(2);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.alibaba.demo.basic;
|
||||
|
||||
import com.alibaba.demo.basic.model.omni.Child;
|
||||
import com.alibaba.demo.basic.model.omni.Parent;
|
||||
import com.alibaba.testable.core.tool.OmniAccessor;
|
||||
import com.alibaba.testable.core.tool.OmniConstructor;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
/**
|
||||
* 演示快速创建任意对象和使用路径访问成员
|
||||
* Demonstrate quick object construction and access members by path
|
||||
*/
|
||||
class DemoOmniMethodsTest {
|
||||
|
||||
@Test
|
||||
void should_construct_any_class() {
|
||||
Parent parent = OmniConstructor.newInstance(Parent.class);
|
||||
|
||||
// 任意深度的子孙成员对象都不为空
|
||||
assertNotNull(parent.getChild().getGrandChild().getContent());
|
||||
|
||||
// 所有基础类型初始化为默认数值
|
||||
assertEquals(0, parent.getChild().getGrandChild().getValue());
|
||||
assertEquals("", parent.getChild().getGrandChild().getContent());
|
||||
|
||||
// 所有数组类型初始化为空数组
|
||||
assertEquals(0, parent.getChildren().length);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_get_any_member() {
|
||||
Parent parent = OmniConstructor.newInstance(Parent.class);
|
||||
parent.setChildren(OmniConstructor.newArray(Child.class, 3));
|
||||
parent.getChild().getGrandChild().setContent("from child");
|
||||
parent.getChildren()[0].getGrandChild().setContent("from 1st children");
|
||||
parent.getChildren()[1].getGrandChild().setContent("from 2nd children");
|
||||
parent.getChildren()[2].getGrandChild().setContent("from 3rd children");
|
||||
|
||||
// 使用成员名称快速读取成员对象
|
||||
List<String> contents = OmniAccessor.get(parent, "content");
|
||||
assertEquals(4, contents.size());
|
||||
assertEquals("from child", contents.get(0));
|
||||
assertEquals("from 1st children", contents.get(1));
|
||||
assertEquals("from 2nd children", contents.get(2));
|
||||
assertEquals("from 3rd children", contents.get(3));
|
||||
|
||||
// 使用成员类型快速读取成员对象
|
||||
contents = OmniAccessor.get(parent, "{Child}/{GrandChild}/content");
|
||||
assertEquals(1, contents.size());
|
||||
assertEquals("from child", contents.get(0));
|
||||
|
||||
// 使用带下标的路径读取成员对象
|
||||
assertEquals("from 2nd children", OmniAccessor.getFirst(parent, "children[1]/{GrandChild}/content"));
|
||||
assertEquals("from 3rd children", OmniAccessor.getFirst(parent, "{Child[]}[2]/{GrandChild}/content"));
|
||||
|
||||
// 使用模糊路径快速读取成员对象
|
||||
assertEquals("from 1st children", OmniAccessor.getFirst(parent, "{C*[]}[0]/*/con*t"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_set_any_member() {
|
||||
Parent parent = OmniConstructor.newInstance(Parent.class);
|
||||
parent.setChildren(OmniConstructor.newArray(Child.class, 3));
|
||||
|
||||
// 使用指定路径快速给成员对象赋值
|
||||
OmniAccessor.set(parent, "child/grandChild/content", "demo child");
|
||||
assertEquals("demo child", parent.getChild().getGrandChild().getContent());
|
||||
|
||||
// 使用带下标的路径给成员对象赋值
|
||||
OmniAccessor.set(parent, "children[1]/grandChild/content", "demo children[1]");
|
||||
assertEquals("demo children[1]", parent.getChildren()[1].getGrandChild().getContent());
|
||||
|
||||
// 使用模糊路径批量给成员对象赋值
|
||||
OmniAccessor.set(parent, "child*/*/content", "demo in batch");
|
||||
assertEquals("demo in batch", parent.getChild().getGrandChild().getContent());
|
||||
assertEquals("demo in batch", parent.getChildren()[0].getGrandChild().getContent());
|
||||
assertEquals("demo in batch", parent.getChildren()[1].getGrandChild().getContent());
|
||||
assertEquals("demo in batch", parent.getChildren()[2].getGrandChild().getContent());
|
||||
|
||||
// 读写私有内部类类型的成员(使用类型名引用内部类时,无需带外部类名)
|
||||
assertEquals("", OmniAccessor.getFirst(parent, "subChild/secret"));
|
||||
OmniAccessor.set(parent, "{InnerChild}/secret", "inner-class secret");
|
||||
assertEquals("inner-class secret", OmniAccessor.getFirst(parent, "subChild/secret"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.alibaba.demo.basic;
|
||||
|
||||
import com.alibaba.testable.core.tool.PrivateAccessor;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
/**
|
||||
* 演示使用`PrivateAccessor`工具类访问私有成员
|
||||
* Demonstrate access private member via `PrivateAccessor` class
|
||||
*/
|
||||
class DemoPrivateAccessorTest {
|
||||
|
||||
private DemoPrivateAccess demoPrivateAccess = new DemoPrivateAccess();
|
||||
|
||||
@Test
|
||||
void should_access_private_method() {
|
||||
List<String> list = new ArrayList<String>() {{ add("a"); add("b"); add("c"); }};
|
||||
assertEquals("member", PrivateAccessor.invoke(demoPrivateAccess, "privateFunc"));
|
||||
assertEquals("abc + hello + 1", PrivateAccessor.invoke(demoPrivateAccess, "privateFuncWithArgs", list, "hello", 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_access_private_field() {
|
||||
PrivateAccessor.set(demoPrivateAccess, "count", 3);
|
||||
assertEquals(Integer.valueOf(3), PrivateAccessor.get(demoPrivateAccess, "count"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_access_private_static_method() {
|
||||
assertEquals("static", PrivateAccessor.invokeStatic(DemoPrivateAccess.class, "privateStaticFunc"));
|
||||
assertEquals("hello + 1", PrivateAccessor.invokeStatic(DemoPrivateAccess.class, "privateStaticFuncWithArgs", "hello", 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_access_private_static_field() {
|
||||
PrivateAccessor.setStatic(DemoPrivateAccess.class, "staticCount", 3);
|
||||
assertEquals(Integer.valueOf(3), PrivateAccessor.getStatic(DemoPrivateAccess.class, "staticCount"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_update_final_field() {
|
||||
PrivateAccessor.set(demoPrivateAccess, "pi", 3.14);
|
||||
assertEquals(Double.valueOf(3.14), PrivateAccessor.get(demoPrivateAccess, "pi"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_use_null_parameter() {
|
||||
PrivateAccessor.set(demoPrivateAccess, "pi", null);
|
||||
assertNull(PrivateAccessor.get(demoPrivateAccess, "pi"));
|
||||
assertEquals("null + 1", PrivateAccessor.invokeStatic(DemoPrivateAccess.class, "privateStaticFuncWithArgs", null, 1));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.alibaba.demo.basic;
|
||||
|
||||
import com.alibaba.testable.processor.annotation.EnablePrivateAccess;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
/**
|
||||
* 演示使用`@EnablePrivateAccess`注解访问私有成员
|
||||
* Demonstrate access private member via `@EnablePrivateAccess` annotation
|
||||
*/
|
||||
@EnablePrivateAccess(srcClass = DemoPrivateAccess.class)
|
||||
class DemoPrivateProcessorTest {
|
||||
|
||||
private DemoPrivateAccess demoPrivateAccess = new DemoPrivateAccess();
|
||||
|
||||
@Test
|
||||
void should_access_private_method() {
|
||||
List<String> list = new ArrayList<String>() {{ add("a"); add("b"); add("c"); }};
|
||||
assertEquals("member", demoPrivateAccess.privateFunc());
|
||||
assertEquals("abc + hello + 1", demoPrivateAccess.privateFuncWithArgs(list, "hello", 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_access_private_field() {
|
||||
demoPrivateAccess.count = 2;
|
||||
assertEquals(Integer.valueOf(2), demoPrivateAccess.count);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_access_private_static_method() {
|
||||
assertEquals("static", DemoPrivateAccess.privateStaticFunc());
|
||||
assertEquals("hello + 1", DemoPrivateAccess.privateStaticFuncWithArgs("hello", 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_access_private_static_field() {
|
||||
DemoPrivateAccess.staticCount = 2;
|
||||
assertEquals(Integer.valueOf(2), DemoPrivateAccess.staticCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_update_final_field() {
|
||||
demoPrivateAccess.pi = 4.13;
|
||||
assertEquals(Double.valueOf(4.13), demoPrivateAccess.pi);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_use_null_parameter() {
|
||||
demoPrivateAccess.pi = null;
|
||||
assertNull(demoPrivateAccess.pi);
|
||||
assertEquals("null + 1", DemoPrivateAccess.privateStaticFuncWithArgs(null, 1));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.alibaba.demo.basic;
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockConstructor;
|
||||
import com.alibaba.testable.core.annotation.MockMethod;
|
||||
import com.alibaba.demo.basic.DemoTemplate;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* 演示模板方法的Mock场景
|
||||
* Demonstrate scenario of mocking template method
|
||||
*/
|
||||
class DemoTemplateTest {
|
||||
|
||||
private DemoTemplate demoTemplate = new DemoTemplate();
|
||||
|
||||
public static class Mock {
|
||||
/* 第一种写法:使用泛型定义 */
|
||||
/* First solution: use generics type */
|
||||
|
||||
@MockMethod
|
||||
private <T> List<T> getList(DemoTemplate self, T value) {
|
||||
return new ArrayList<T>() {{ add((T)(value.toString() + "_mock_list")); }};
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
private <K, V> Map<K, V> getMap(DemoTemplate self, K key, V value) {
|
||||
return new HashMap<K, V>() {{ put(key, (V)(value.toString() + "_mock_map")); }};
|
||||
}
|
||||
|
||||
@MockConstructor
|
||||
private <T> HashSet<T> newHashSet() {
|
||||
HashSet<T> set = new HashSet<>();
|
||||
set.add((T)"insert_mock");
|
||||
return set;
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
private <E> boolean add(Set s, E e) {
|
||||
s.add(e.toString() + "_mocked");
|
||||
return true;
|
||||
}
|
||||
|
||||
/* 第二种写法:使用Object类型 */
|
||||
/* Second solution: use object type */
|
||||
|
||||
//@MockMethod
|
||||
//private List<Object> getList(DemoTemplate self, Object value) {
|
||||
// return new ArrayList<Object>() {{ add(value.toString() + "_mock_list"); }};
|
||||
//}
|
||||
//
|
||||
//@MockMethod
|
||||
//private Map<Object, Object> getMap(DemoTemplate self, Object key, Object value) {
|
||||
// return new HashMap<Object, Object>() {{ put(key, value.toString() + "_mock_map"); }};
|
||||
//}
|
||||
//
|
||||
//@MockConstructor
|
||||
//private HashSet newHashSet() {
|
||||
// HashSet<Object> set = new HashSet<>();
|
||||
// set.add("insert_mock");
|
||||
// return set;
|
||||
//}
|
||||
//
|
||||
//@MockMethod
|
||||
//private boolean add(Set s, Object e) {
|
||||
// s.add(e.toString() + "_mocked");
|
||||
// return true;
|
||||
//}
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_mock_single_template_method() {
|
||||
String res = demoTemplate.singleTemplateMethod();
|
||||
assertEquals("demo_mock_list", res);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_mock_double_template_method() {
|
||||
String res = demoTemplate.doubleTemplateMethod();
|
||||
assertEquals("testable_mock_map", res);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_mock_new_template_method() {
|
||||
Set<?> res = demoTemplate.newTemplateMethod();
|
||||
assertEquals(2, res.size());
|
||||
Iterator<?> iterator = res.stream().iterator();
|
||||
assertEquals("insert_mock", iterator.next());
|
||||
assertEquals("world_mocked", iterator.next());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.alibaba.demo.one2multi;
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockMethod;
|
||||
|
||||
public class ASvcMock {
|
||||
|
||||
@MockMethod(targetClass = String.class, targetMethod = "format")
|
||||
public String a_format(String format, Object... args) {
|
||||
return "a_mock";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.alibaba.demo.one2multi;
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockMethod;
|
||||
|
||||
public class BSvcMock {
|
||||
|
||||
@MockMethod(targetClass = String.class, targetMethod = "format")
|
||||
public String b_format(String format, Object... args) {
|
||||
return "b_mock";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.alibaba.demo.one2multi;
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockMethod;
|
||||
|
||||
public class CSvcMock {
|
||||
|
||||
@MockMethod(targetClass = String.class, targetMethod = "format")
|
||||
public String c_format(String format, Object... args) {
|
||||
return "c_mock";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.alibaba.demo.one2multi;
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
||||
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@MockWith
|
||||
public class OneToMultiSvcTest {
|
||||
|
||||
private ASvc aSvc = new ASvc();
|
||||
private BSvc bSvc = new BSvc();
|
||||
private CSvc cSvc = new CSvc();
|
||||
|
||||
@Test
|
||||
public void should_test_multi_class_together() {
|
||||
assertEquals("a_mock", aSvc.demo("test"));
|
||||
assertEquals("b_mock", bSvc.demo("test"));
|
||||
assertEquals("c_mock", cSvc.demo("test"));
|
||||
verify("a_format").withTimes(1);
|
||||
verify("b_format").withTimes(1);
|
||||
verify("c_format").withTimes(1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package com.alibaba.testable.demo;
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockConstructor;
|
||||
import com.alibaba.testable.core.annotation.MockMethod;
|
||||
import com.alibaba.testable.demo.model.BlackBox;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
|
||||
import static com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD;
|
||||
import static com.alibaba.testable.core.tool.TestableTool.TEST_CASE;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* 演示基本的Mock功能
|
||||
* Demonstrate basic mock functionality
|
||||
*/
|
||||
class DemoMockTest {
|
||||
|
||||
private DemoMock demoMock = new DemoMock();
|
||||
|
||||
@MockConstructor
|
||||
private BlackBox createBlackBox(String text) {
|
||||
return new BlackBox("mock_" + text);
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
private String innerFunc(DemoMock self, String text) {
|
||||
return "mock_" + text;
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
private String staticFunc(DemoMock self) {
|
||||
return "_MOCK_TAIL";
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
private String trim(String self) {
|
||||
return "trim_string";
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "substring")
|
||||
private String sub(String self, int i, int j) {
|
||||
return "sub_string";
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
private boolean startsWith(String self, String s) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
private BlackBox secretBox(BlackBox ignore) {
|
||||
return new BlackBox("not_secret_box");
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
private String callFromDifferentMethod(DemoMock self) {
|
||||
if (TEST_CASE.equals("should_able_to_get_test_case_name")) {
|
||||
return "mock_special";
|
||||
}
|
||||
switch (SOURCE_METHOD) {
|
||||
case "callerOne": return "mock_one";
|
||||
default: return "mock_others";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void should_able_to_mock_new_object() {
|
||||
assertEquals("mock_something", demoMock.newFunc());
|
||||
verify("createBlackBox").with("something");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_able_to_mock_member_method() throws Exception {
|
||||
assertEquals("{ \"res\": \"mock_hello_MOCK_TAIL\"}", demoMock.outerFunc("hello"));
|
||||
verify("innerFunc").with("hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_able_to_mock_common_method() {
|
||||
assertEquals("trim_string__sub_string__false", demoMock.commonFunc());
|
||||
verify("trim").withTimes(1);
|
||||
verify("sub").withTimes(1);
|
||||
verify("startsWith").withTimes(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_able_to_mock_static_method() {
|
||||
assertEquals("not_secret_box", demoMock.getBox().get());
|
||||
verify("secretBox").withTimes(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_able_to_get_source_method_name() throws Exception {
|
||||
// synchronous
|
||||
assertEquals("mock_one_mock_others", demoMock.callerOne() + "_" + demoMock.callerTwo());
|
||||
// asynchronous
|
||||
assertEquals("mock_one_mock_others",
|
||||
Executors.newSingleThreadExecutor().submit(() -> demoMock.callerOne() + "_" + demoMock.callerTwo()).get());
|
||||
verify("callFromDifferentMethod").withTimes(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_able_to_get_test_case_name() throws Exception {
|
||||
// synchronous
|
||||
assertEquals("mock_special", demoMock.callerOne());
|
||||
// asynchronous
|
||||
assertEquals("mock_special", Executors.newSingleThreadExecutor().submit(() -> demoMock.callerOne()).get());
|
||||
verify("callFromDifferentMethod").withTimes(2);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package com.alibaba.testable.demo;
|
||||
|
||||
import com.alibaba.testable.core.accessor.PrivateAccessor;
|
||||
import com.alibaba.testable.processor.annotation.EnablePrivateAccess;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* 演示私有成员访问功能
|
||||
* Demonstrate private member access functionality
|
||||
*/
|
||||
@EnablePrivateAccess
|
||||
class DemoPrivateAccessTest {
|
||||
|
||||
private DemoPrivateAccess demoPrivateAccess = new DemoPrivateAccess();
|
||||
|
||||
@Test
|
||||
void should_able_to_access_private_method() {
|
||||
assertEquals("hello - 1", demoPrivateAccess.privateFunc("hello", 1));
|
||||
assertEquals("hello - 1", PrivateAccessor.invoke(demoPrivateAccess, "privateFunc", "hello", 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_able_to_access_private_field() {
|
||||
demoPrivateAccess.count = 2;
|
||||
assertEquals(new Integer(2), demoPrivateAccess.count);
|
||||
|
||||
PrivateAccessor.set(demoPrivateAccess, "count", 3);
|
||||
assertEquals(new Integer(3), PrivateAccessor.get(demoPrivateAccess, "count"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_able_to_access_private_static_method() {
|
||||
assertEquals("hello + 1", DemoPrivateAccess.privateStaticFunc("hello", 1));
|
||||
assertEquals("hello + 1", PrivateAccessor.invokeStatic(DemoPrivateAccess.class, "privateStaticFunc", "hello", 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_able_to_access_private_static_field() {
|
||||
DemoPrivateAccess.staticCount = 2;
|
||||
assertEquals(new Integer(2), DemoPrivateAccess.staticCount);
|
||||
|
||||
PrivateAccessor.setStatic(DemoPrivateAccess.class, "staticCount", 3);
|
||||
assertEquals(new Integer(3), PrivateAccessor.getStatic(DemoPrivateAccess.class, "staticCount"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_able_to_update_final_field() {
|
||||
demoPrivateAccess.pi = 4.13;
|
||||
assertEquals(4.13, demoPrivateAccess.pi);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package com.alibaba.testable.demo;
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockConstructor;
|
||||
import com.alibaba.testable.core.annotation.MockMethod;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* 演示模板方法的Mock场景
|
||||
* Demonstrate scenario of mocking template method
|
||||
*/
|
||||
class DemoTemplateTest {
|
||||
|
||||
private DemoTemplate demoTemplate = new DemoTemplate();
|
||||
|
||||
/* 第一种写法:使用泛型定义 */
|
||||
/* First solution: use generics type */
|
||||
|
||||
@MockMethod
|
||||
private <T> List<T> getList(DemoTemplate self, T value) {
|
||||
return new ArrayList<T>() {{ add((T)(value.toString() + "_mock_list")); }};
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
private <K, V> Map<K, V> getMap(DemoTemplate self, K key, V value) {
|
||||
return new HashMap<K, V>() {{ put(key, (V)(value.toString() + "_mock_map")); }};
|
||||
}
|
||||
|
||||
@MockConstructor
|
||||
private <T> HashSet<T> newHashSet() {
|
||||
HashSet<T> set = new HashSet<>();
|
||||
set.add((T)"insert_mock");
|
||||
return set;
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
private <E> boolean add(Set s, E e) {
|
||||
s.add(e.toString() + "_mocked");
|
||||
return true;
|
||||
}
|
||||
|
||||
/* 第二种写法:使用Object类型 */
|
||||
/* Second solution: use object type */
|
||||
|
||||
//@MockMethod
|
||||
//private List<Object> getList(DemoTemplate self, Object value) {
|
||||
// return new ArrayList<Object>() {{ add(value.toString() + "_mock_list"); }};
|
||||
//}
|
||||
//
|
||||
//@MockMethod
|
||||
//private Map<Object, Object> getMap(DemoTemplate self, Object key, Object value) {
|
||||
// return new HashMap<Object, Object>() {{ put(key, value.toString() + "_mock_map"); }};
|
||||
//}
|
||||
//
|
||||
//@MockConstructor
|
||||
//private HashSet newHashSet() {
|
||||
// HashSet<Object> set = new HashSet<>();
|
||||
// set.add("insert_mock");
|
||||
// return set;
|
||||
//}
|
||||
//
|
||||
//@MockMethod
|
||||
//private boolean add(Set s, Object e) {
|
||||
// s.add(e.toString() + "_mocked");
|
||||
// return true;
|
||||
//}
|
||||
|
||||
|
||||
@Test
|
||||
void should_able_to_mock_single_template_method() {
|
||||
String res = demoTemplate.singleTemplateMethod();
|
||||
assertEquals("demo_mock_list", res);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_able_to_mock_double_template_method() {
|
||||
String res = demoTemplate.doubleTemplateMethod();
|
||||
assertEquals("testable_mock_map", res);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_able_to_mock_new_template_method() {
|
||||
Set<?> res = demoTemplate.newTemplateMethod();
|
||||
assertEquals(2, res.size());
|
||||
Iterator<?> iterator = res.stream().iterator();
|
||||
assertEquals("insert_mock", iterator.next());
|
||||
assertEquals("world_mocked", iterator.next());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "1.4.10"
|
||||
kotlin("jvm") version "1.4.10"
|
||||
}
|
||||
|
||||
group = "com.alibaba.testable"
|
||||
@@ -9,25 +9,25 @@ version = "1.0.0-SNAPSHOT"
|
||||
java.sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter:5.6.2")
|
||||
testImplementation("com.alibaba.testable:testable-all:0.4.4")
|
||||
testAnnotationProcessor("com.alibaba.testable:testable-processor:0.4.4")
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter:5.6.2")
|
||||
testImplementation("com.alibaba.testable:testable-all:0.6.1")
|
||||
testAnnotationProcessor("com.alibaba.testable:testable-processor:0.6.1")
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
kotlinOptions {
|
||||
freeCompilerArgs = listOf("-Xjsr305=strict")
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
kotlinOptions {
|
||||
freeCompilerArgs = listOf("-Xjsr305=strict")
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<Test> {
|
||||
jvmArgs("-javaagent:${classpath.find { it.name.contains("testable-agent") }!!.absolutePath}")
|
||||
useJUnitPlatform()
|
||||
jvmArgs("-javaagent:${classpath.find { it.name.contains("testable-agent") }!!.absolutePath}")
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
@@ -1,119 +1,119 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>kotlin-demo</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<name>kotlin-demo</name>
|
||||
<description>Demo project for TestableMock</description>
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>kotlin-demo</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<name>kotlin-demo</name>
|
||||
<description>Demo project for TestableMock</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<kotlin.version>1.3.72</kotlin.version>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<junit.version>5.6.2</junit.version>
|
||||
<testable.version>0.4.4</testable.version>
|
||||
</properties>
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<kotlin.version>1.3.72</kotlin.version>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<junit.version>5.6.2</junit.version>
|
||||
<testable.version>0.6.1</testable.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-reflect</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-reflect</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-all</artifactId>
|
||||
<version>${testable.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-all</artifactId>
|
||||
<version>${testable.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<sourceDirectory>src/main/kotlin</sourceDirectory>
|
||||
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0-M5</version>
|
||||
<!-- <configuration>-->
|
||||
<!-- <argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>-->
|
||||
<!-- </configuration>-->
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-maven-plugin</artifactId>
|
||||
<version>${testable.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>prepare</id>
|
||||
<goals>
|
||||
<goal>prepare</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.8.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>prepare-agent</id>
|
||||
<goals>
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>report</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<dataFile>target/jacoco.exec</dataFile>
|
||||
<outputDirectory>target/jacoco-ut</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>compile</id>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>test-compile</id>
|
||||
<phase>test-compile</phase>
|
||||
<goals>
|
||||
<goal>test-compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<build>
|
||||
<sourceDirectory>src/main/kotlin</sourceDirectory>
|
||||
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0-M5</version>
|
||||
<!-- <configuration>-->
|
||||
<!-- <argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>-->
|
||||
<!-- </configuration>-->
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-maven-plugin</artifactId>
|
||||
<version>${testable.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>prepare</id>
|
||||
<goals>
|
||||
<goal>prepare</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.8.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>prepare-agent</id>
|
||||
<goals>
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>report</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<dataFile>target/jacoco.exec</dataFile>
|
||||
<outputDirectory>target/jacoco-ut</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>compile</id>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>test-compile</id>
|
||||
<phase>test-compile</phase>
|
||||
<goals>
|
||||
<goal>test-compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
rootProject.name = "demo"
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.alibaba.testable.demo
|
||||
package com.alibaba.demo.basic
|
||||
|
||||
import com.alibaba.testable.demo.model.BlackBox
|
||||
import com.alibaba.testable.demo.model.Box
|
||||
import com.alibaba.testable.demo.model.Color
|
||||
import com.alibaba.demo.basic.model.mock.BlackBox
|
||||
import com.alibaba.demo.basic.model.mock.Box
|
||||
import com.alibaba.demo.basic.model.mock.Color
|
||||
|
||||
/**
|
||||
* 演示父类变量引用子类对象时的Mock场景
|
||||
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
|
||||
*/
|
||||
class DemoInherit {
|
||||
|
||||
/**
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.alibaba.demo.basic
|
||||
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
/**
|
||||
* 演示对内部类的Mock支持
|
||||
* Demonstrate support for mocking invocation inside a inner class
|
||||
*/
|
||||
class DemoInnerClass {
|
||||
|
||||
class StaticInner {
|
||||
/**
|
||||
* invocation inside a static inner class
|
||||
*/
|
||||
fun demo(): String {
|
||||
return methodToBeMock()
|
||||
}
|
||||
}
|
||||
|
||||
inner class Inner {
|
||||
/**
|
||||
* invocation inside a non-static inner class
|
||||
*/
|
||||
fun demo(): String {
|
||||
return methodToBeMock()
|
||||
}
|
||||
}
|
||||
|
||||
private val executorService = Executors.newSingleThreadExecutor()
|
||||
@Throws(ExecutionException::class, InterruptedException::class)
|
||||
fun callAnonymousInner(): String {
|
||||
/**
|
||||
* invocation inside a anonymous inner class
|
||||
*/
|
||||
val future = executorService.submit<String> { methodToBeMock() }
|
||||
return future.get()
|
||||
}
|
||||
|
||||
fun callInnerDemo(): String {
|
||||
return Inner().demo()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun methodToBeMock(): String {
|
||||
return "RealCall"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package com.alibaba.testable.demo
|
||||
package com.alibaba.demo.basic
|
||||
|
||||
import com.alibaba.testable.demo.model.BlackBox
|
||||
import com.alibaba.demo.basic.model.mock.BlackBox
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
* 演示Mock方法调用校验器
|
||||
* Demonstrate mock method invocation verifier
|
||||
*/
|
||||
class DemoMatcher {
|
||||
/**
|
||||
@@ -1,10 +1,14 @@
|
||||
package com.alibaba.testable.demo
|
||||
package com.alibaba.demo.basic
|
||||
|
||||
import com.alibaba.testable.demo.model.BlackBox
|
||||
import com.alibaba.testable.demo.model.ColorBox
|
||||
import com.alibaba.demo.basic.model.mock.BlackBox
|
||||
import com.alibaba.demo.basic.model.mock.ColorBox
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
|
||||
/**
|
||||
* 演示基本的Mock功能
|
||||
* Demonstrate basic mock functionality
|
||||
*/
|
||||
class DemoMock {
|
||||
|
||||
/**
|
||||
@@ -18,7 +22,7 @@ class DemoMock {
|
||||
* method with member method invoke
|
||||
*/
|
||||
fun outerFunc(s: String): String {
|
||||
return "{ \"res\": \"" + innerFunc(s) + "\"}"
|
||||
return "{ \"res\": \"" + innerFunc(s) + staticFunc() + "\"}"
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,4 +56,14 @@ class DemoMock {
|
||||
}
|
||||
|
||||
private fun callFromDifferentMethod() = "realOne"
|
||||
|
||||
companion object {
|
||||
private fun staticFunc(): String {
|
||||
return "_STATIC_TAIL"
|
||||
}
|
||||
|
||||
// fun callStaticFunc(): String {
|
||||
// return "CALL${staticFunc()}"
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.alibaba.demo.basic
|
||||
|
||||
/**
|
||||
* 演示私有成员访问功能
|
||||
* Demonstrate private member access functionality
|
||||
*/
|
||||
class DemoPrivateAccess {
|
||||
|
||||
/**
|
||||
* a private member field
|
||||
*/
|
||||
var count = 0
|
||||
|
||||
/**
|
||||
* a constant field
|
||||
*/
|
||||
val pi = 3.14
|
||||
|
||||
/**
|
||||
* private member method
|
||||
*/
|
||||
fun privateFunc(list: List<String>, str: String, i: Int): String {
|
||||
return list.reduce { a: String, s: String -> a + s } + " + " + "$str + $i"
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* a private static field
|
||||
*/
|
||||
var staticCount = 0
|
||||
|
||||
/**
|
||||
* private static method
|
||||
*/
|
||||
fun privateStaticFunc(str: String, i: Int): String {
|
||||
return "$str + $i"
|
||||
}
|
||||
|
||||
/**
|
||||
* private jvm static method
|
||||
*/
|
||||
@JvmStatic private fun privateJvmStaticFunc(list: List<String>, str: String, i: Int): String {
|
||||
return list.reduce { a: String, s: String -> a + s } + " * " + "$str * $i"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.alibaba.testable.demo
|
||||
package com.alibaba.demo.basic
|
||||
|
||||
import java.util.ArrayList
|
||||
import java.util.HashMap
|
||||
import java.util.HashSet
|
||||
|
||||
/**
|
||||
* 演示模板方法的Mock场景
|
||||
* Demonstrate scenario of mocking template method
|
||||
*/
|
||||
class DemoTemplate {
|
||||
private fun <T> getList(value: T): List<T> {
|
||||
val l: MutableList<T> = ArrayList()
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.testable.demo.model
|
||||
package com.alibaba.demo.basic.model.mock
|
||||
|
||||
|
||||
class BlackBox(var input: String) : Box(), Color {
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.testable.demo.model
|
||||
package com.alibaba.demo.basic.model.mock
|
||||
|
||||
abstract class Box {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.alibaba.testable.demo.model
|
||||
package com.alibaba.demo.basic.model.mock
|
||||
|
||||
interface Color {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.alibaba.demo.basic.model.omni
|
||||
|
||||
/**
|
||||
* 我是一个只有私有构造方法的类
|
||||
* This class have only private constructor
|
||||
*/
|
||||
class Child private constructor() {
|
||||
|
||||
// ---------- 内部成员字段 ----------
|
||||
var grandChild: GrandChild? = null
|
||||
var subChild: InnerChild? = null
|
||||
|
||||
/**
|
||||
* 这是一个私有内部类
|
||||
* An private inner class
|
||||
*/
|
||||
inner class InnerChild {
|
||||
private val secret: String? = null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.alibaba.demo.basic.model.omni
|
||||
|
||||
class GrandChild {
|
||||
|
||||
// ---------- 内部成员字段 ----------
|
||||
var value = 0
|
||||
var content: String? = null
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.alibaba.demo.basic.model.omni
|
||||
|
||||
import java.lang.IllegalArgumentException
|
||||
|
||||
/**
|
||||
* 我是一个虽有构造方法,但无法正常构造的类
|
||||
* This class have constructor with exception throw
|
||||
*/
|
||||
class Parent {
|
||||
|
||||
init {
|
||||
throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
// ---------- 内部成员字段 ----------
|
||||
var child: Child? = null
|
||||
var children: Array<Child>? = null
|
||||
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.alibaba.testable.demo.util
|
||||
package com.alibaba.demo.java2kotlin
|
||||
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
object PathUtil {
|
||||
object PathDemo {
|
||||
|
||||
fun deleteRecursively(file: File) {
|
||||
if (!file.exists()) {
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.alibaba.demo.one2multi
|
||||
|
||||
class ASvc {
|
||||
|
||||
fun demo(name: String): String {
|
||||
return String.format("a_%s", name)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.alibaba.demo.one2multi
|
||||
|
||||
class BSvc {
|
||||
|
||||
fun demo(name: String): String {
|
||||
return String.format("b_%s", name)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.alibaba.demo.one2multi
|
||||
|
||||
class CSvc {
|
||||
|
||||
fun demo(name: String): String {
|
||||
return String.format("c_%s", name)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.alibaba.testable.demo
|
||||
|
||||
class DemoPrivateAccess {
|
||||
|
||||
/**
|
||||
* a private member field
|
||||
*/
|
||||
private var count = 0
|
||||
|
||||
/**
|
||||
* a constant field
|
||||
*/
|
||||
val pi = 3.14
|
||||
|
||||
/**
|
||||
* private member method
|
||||
*/
|
||||
private fun privateFunc(s: String, i: Int): String {
|
||||
return "$s - $i"
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* a private static field
|
||||
*/
|
||||
private var staticCount = 0
|
||||
|
||||
/**
|
||||
* private static method
|
||||
*/
|
||||
private fun privateStaticFunc(s: String, i: Int): String {
|
||||
return "$s + $i"
|
||||
}
|
||||
|
||||
/**
|
||||
* private jvm static method
|
||||
*/
|
||||
@JvmStatic private fun privateJvmStaticFunc(s: String, i: Int): String {
|
||||
return "$s * $i"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.alibaba.demo.basic
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockMethod
|
||||
import com.alibaba.testable.core.matcher.InvokeVerifier
|
||||
import com.alibaba.demo.basic.model.mock.BlackBox
|
||||
import com.alibaba.demo.basic.model.mock.Box
|
||||
import com.alibaba.demo.basic.model.mock.Color
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
/**
|
||||
* 演示父类变量引用子类对象时的Mock场景
|
||||
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
|
||||
*/
|
||||
internal class DemoInheritTest {
|
||||
|
||||
private val demoInherit = com.alibaba.demo.basic.DemoInherit()
|
||||
|
||||
class Mock {
|
||||
@MockMethod(targetMethod = "put")
|
||||
private fun put_into_box(self: Box, something: String) {
|
||||
self.put("put_" + something + "_into_box")
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "put")
|
||||
private fun put_into_blackbox(self: BlackBox, something: String) {
|
||||
self.put("put_" + something + "_into_blackbox")
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "get")
|
||||
private fun get_from_box(self: Box): String {
|
||||
return "get_from_box"
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "get")
|
||||
private fun get_from_blackbox(self: BlackBox): String {
|
||||
return "get_from_blackbox"
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "getColor")
|
||||
private fun get_color_from_color(self: Color): String {
|
||||
return "color_from_color"
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "getColor")
|
||||
private fun get_color_from_blackbox(self: BlackBox): String {
|
||||
return "color_from_blackbox"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_mock_call_sub_object_method_by_parent_object() {
|
||||
val box = demoInherit.putIntoBox() as BlackBox
|
||||
InvokeVerifier.verify("put_into_box").withTimes(1)
|
||||
Assertions.assertEquals("put_data_into_box", box.get())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_mock_call_sub_object_method_by_sub_object() {
|
||||
val box = demoInherit.putIntoBlackBox()
|
||||
InvokeVerifier.verify("put_into_blackbox").withTimes(1)
|
||||
Assertions.assertEquals("put_data_into_blackbox", box.get())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_mock_call_parent_object_method_by_parent_object() {
|
||||
val content = demoInherit.fromBox
|
||||
InvokeVerifier.verify("get_from_box").withTimes(1)
|
||||
Assertions.assertEquals("get_from_box", content)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_mock_call_parent_object_method_by_sub_object() {
|
||||
val content = demoInherit.fromBlackBox
|
||||
InvokeVerifier.verify("get_from_blackbox").withTimes(1)
|
||||
Assertions.assertEquals("get_from_blackbox", content)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_mock_call_interface_method_by_interface_object() {
|
||||
val color = demoInherit.colorViaColor
|
||||
InvokeVerifier.verify("get_color_from_color").withTimes(1)
|
||||
Assertions.assertEquals("color_from_color", color)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_mock_call_interface_method_by_sub_class_object() {
|
||||
val color = demoInherit.colorViaBox
|
||||
InvokeVerifier.verify("get_color_from_blackbox").withTimes(1)
|
||||
Assertions.assertEquals("color_from_blackbox", color)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.alibaba.demo.basic
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockMethod
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
/**
|
||||
* 演示对内部类的Mock支持
|
||||
* Demonstrate support for mocking invocation inside a inner class
|
||||
*/
|
||||
internal class DemoInnerClassTest {
|
||||
|
||||
class Mock {
|
||||
@MockMethod(targetClass = DemoInnerClass::class)
|
||||
fun methodToBeMock(): String {
|
||||
return "MockedCall"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun should_mock_invoke_inside_inner_class() {
|
||||
val demo = DemoInnerClass()
|
||||
Assertions.assertEquals("MockedCall", demo.callInnerDemo())
|
||||
Assertions.assertEquals("MockedCall", demo.callAnonymousInner())
|
||||
Assertions.assertEquals("MockedCall", DemoInnerClass.StaticInner().demo())
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.alibaba.testable.demo
|
||||
package com.alibaba.demo.basic
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockMethod
|
||||
import com.alibaba.testable.core.error.VerifyFailedError
|
||||
import com.alibaba.testable.core.matcher.InvokeMatcher
|
||||
import com.alibaba.testable.core.matcher.InvokeVerifier
|
||||
import com.alibaba.testable.demo.model.BlackBox
|
||||
import com.alibaba.demo.basic.model.mock.BlackBox
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@@ -14,21 +14,22 @@ import org.junit.jupiter.api.Test
|
||||
*/
|
||||
internal class DemoMatcherTest {
|
||||
|
||||
private val demoMatcher = DemoMatcher()
|
||||
private val demoMatcher = com.alibaba.demo.basic.DemoMatcher()
|
||||
|
||||
@MockMethod(targetMethod = "methodToBeMocked")
|
||||
private fun methodWithoutArgument(self: DemoMatcher) {
|
||||
class Mock {
|
||||
@MockMethod(targetMethod = "methodToBeMocked")
|
||||
private fun methodWithoutArgument(self: com.alibaba.demo.basic.DemoMatcher) {
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "methodToBeMocked")
|
||||
private fun methodWithArguments(self: com.alibaba.demo.basic.DemoMatcher, a1: Any, a2: Any) {
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "methodToBeMocked")
|
||||
private fun methodWithArrayArgument(self: com.alibaba.demo.basic.DemoMatcher, a: Array<Any>) {
|
||||
}
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "methodToBeMocked")
|
||||
private fun methodWithArguments(self: DemoMatcher, a1: Any, a2: Any) {
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "methodToBeMocked")
|
||||
private fun methodWithArrayArgument(self: DemoMatcher, a: Array<Any>) {
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun should_match_no_argument() {
|
||||
demoMatcher.callMethodWithoutArgument()
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.alibaba.demo.basic
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockConstructor
|
||||
import com.alibaba.testable.core.annotation.MockMethod
|
||||
import com.alibaba.testable.core.matcher.InvokeVerifier.verify
|
||||
import com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD
|
||||
import com.alibaba.testable.core.tool.TestableTool.MOCK_CONTEXT
|
||||
import com.alibaba.demo.basic.model.mock.BlackBox
|
||||
import com.alibaba.demo.basic.model.mock.ColorBox
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
/**
|
||||
* 演示基本的Mock功能
|
||||
* Demonstrate basic mock functionality
|
||||
*/
|
||||
internal class DemoMockTest {
|
||||
|
||||
private val demoMock = DemoMock()
|
||||
|
||||
class Mock {
|
||||
@MockConstructor
|
||||
private fun createBlackBox(text: String) = BlackBox("mock_$text")
|
||||
|
||||
@MockMethod(targetClass = DemoMock::class)
|
||||
private fun innerFunc(text: String) = "mock_$text"
|
||||
|
||||
@MockMethod(targetClass = DemoMock::class)
|
||||
private fun staticFunc(): String {
|
||||
return "_MOCK_TAIL";
|
||||
}
|
||||
|
||||
@MockMethod(targetClass = BlackBox::class)
|
||||
private fun trim() = "trim_string"
|
||||
|
||||
@MockMethod(targetClass = BlackBox::class, targetMethod = "substring")
|
||||
private fun sub(i: Int, j: Int) = "sub_string"
|
||||
|
||||
@MockMethod(targetClass = BlackBox::class)
|
||||
private fun startsWith(s: String) = false
|
||||
|
||||
@MockMethod(targetClass = BlackBox::class)
|
||||
private fun secretBox(): BlackBox {
|
||||
return BlackBox("not_secret_box")
|
||||
}
|
||||
|
||||
@MockMethod(targetClass = ColorBox::class)
|
||||
private fun createBox(color: String, box: BlackBox): BlackBox {
|
||||
return BlackBox("White_${box.get()}")
|
||||
}
|
||||
|
||||
@MockMethod(targetClass = DemoMock::class)
|
||||
private fun callFromDifferentMethod(): String {
|
||||
return if (MOCK_CONTEXT["case"] == "special_case") {
|
||||
"mock_special"
|
||||
} else {
|
||||
when (SOURCE_METHOD) {
|
||||
"callerOne" -> "mock_one"
|
||||
else -> "mock_others"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_mock_new_object() {
|
||||
assertEquals("mock_something", demoMock.newFunc())
|
||||
verify("createBlackBox").with("something")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_mock_member_method() {
|
||||
assertEquals("{ \"res\": \"mock_hello_MOCK_TAIL\"}", demoMock.outerFunc("hello"))
|
||||
verify("innerFunc").with("hello")
|
||||
verify("staticFunc").with()
|
||||
}
|
||||
|
||||
// @Test
|
||||
// fun should_mock_method_in_companion_object() {
|
||||
// assertEquals("CALL_MOCK_TAIL", DemoMock.callStaticFunc())
|
||||
// verify("staticFunc").with()
|
||||
// }
|
||||
|
||||
@Test
|
||||
fun should_mock_common_method() {
|
||||
assertEquals("trim_string__sub_string__false", demoMock.commonFunc())
|
||||
verify("trim").withTimes(1)
|
||||
verify("sub").withTimes(1)
|
||||
verify("startsWith").withTimes(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_mock_static_method() {
|
||||
assertEquals("White_not_secret_box", demoMock.getBox().get())
|
||||
verify("secretBox").withTimes(1)
|
||||
verify("createBox").withTimes(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_get_source_method_name() {
|
||||
// synchronous
|
||||
assertEquals("mock_one_mock_others", demoMock.callerOne() + "_" + demoMock.callerTwo())
|
||||
// asynchronous
|
||||
assertEquals("mock_one_mock_others", Executors.newSingleThreadExecutor().submit<String> {
|
||||
demoMock.callerOne() + "_" + demoMock.callerTwo()
|
||||
}.get())
|
||||
verify("callFromDifferentMethod").withTimes(4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_get_test_case_name() {
|
||||
MOCK_CONTEXT["case"] = "special_case"
|
||||
// synchronous
|
||||
assertEquals("mock_special", demoMock.callerOne())
|
||||
// asynchronous
|
||||
assertEquals("mock_special", Executors.newSingleThreadExecutor().submit<String> {
|
||||
demoMock.callerOne()
|
||||
}.get())
|
||||
verify("callFromDifferentMethod").withTimes(2)
|
||||
MOCK_CONTEXT.clear()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.alibaba.demo.basic
|
||||
|
||||
import com.alibaba.demo.basic.model.omni.Child
|
||||
import com.alibaba.demo.basic.model.omni.Parent
|
||||
import com.alibaba.testable.core.tool.OmniAccessor
|
||||
import com.alibaba.testable.core.tool.OmniConstructor
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
/**
|
||||
* 演示快速创建任意对象和使用路径访问成员
|
||||
* Demonstrate quick object construction and access members by path
|
||||
*/
|
||||
internal class DemoOmniMethodsTest {
|
||||
|
||||
@Test
|
||||
fun should_construct_any_class() {
|
||||
val parent = OmniConstructor.newInstance(Parent::class.java)
|
||||
|
||||
// 任意深度的子孙成员对象都不为空
|
||||
Assertions.assertNotNull(parent.child?.grandChild?.content)
|
||||
|
||||
// 所有基础类型初始化为默认数值
|
||||
Assertions.assertEquals(0, parent.child?.grandChild?.value)
|
||||
Assertions.assertEquals("", parent.child?.grandChild?.content)
|
||||
|
||||
// 所有数组类型初始化为空数组
|
||||
Assertions.assertEquals(0, parent.children?.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_get_any_member() {
|
||||
val parent = OmniConstructor.newInstance(Parent::class.java)
|
||||
parent.children = OmniConstructor.newArray(Child::class.java, 3)
|
||||
parent.child?.grandChild?.content = "from child"
|
||||
parent.children?.get(0)?.grandChild?.content = "from 1st children"
|
||||
parent.children?.get(1)?.grandChild?.content = "from 2nd children"
|
||||
parent.children?.get(2)?.grandChild?.content = "from 3rd children"
|
||||
|
||||
// 使用成员名称快速读取成员对象
|
||||
var contents = OmniAccessor.get<String?>(parent, "content")
|
||||
Assertions.assertEquals(4, contents.size)
|
||||
Assertions.assertEquals("from child", contents[0])
|
||||
Assertions.assertEquals("from 1st children", contents[1])
|
||||
Assertions.assertEquals("from 2nd children", contents[2])
|
||||
Assertions.assertEquals("from 3rd children", contents[3])
|
||||
|
||||
// 使用成员类型快速读取成员对象
|
||||
contents = OmniAccessor.get(parent, "{Child}/{GrandChild}/content")
|
||||
Assertions.assertEquals(1, contents.size)
|
||||
Assertions.assertEquals("from child", contents[0])
|
||||
|
||||
// 使用带下标的路径读取成员对象
|
||||
Assertions.assertEquals("from 2nd children", OmniAccessor.getFirst(parent, "children[1]/{GrandChild}/content"))
|
||||
Assertions.assertEquals("from 3rd children", OmniAccessor.getFirst(parent, "{Child[]}[2]/{GrandChild}/content"))
|
||||
|
||||
// 使用模糊路径快速读取成员对象
|
||||
Assertions.assertEquals("from 1st children", OmniAccessor.getFirst(parent, "{C*[]}[0]/*/con*t"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_set_any_member() {
|
||||
val parent = OmniConstructor.newInstance(Parent::class.java)
|
||||
parent.children = OmniConstructor.newArray(Child::class.java, 3)
|
||||
|
||||
// 使用指定路径快速给成员对象赋值
|
||||
OmniAccessor.set(parent, "child/grandChild/content", "demo child")
|
||||
Assertions.assertEquals("demo child", parent.child?.grandChild?.content)
|
||||
|
||||
// 使用带下标的路径给成员对象赋值
|
||||
OmniAccessor.set(parent, "children[1]/grandChild/content", "demo children[1]")
|
||||
Assertions.assertEquals("demo children[1]", parent.children?.get(1)?.grandChild?.content)
|
||||
|
||||
// 使用模糊路径批量给成员对象赋值
|
||||
OmniAccessor.set(parent, "child*/*/content", "demo in batch")
|
||||
Assertions.assertEquals("demo in batch", parent.child?.grandChild?.content)
|
||||
Assertions.assertEquals("demo in batch", parent.children?.get(0)?.grandChild?.content)
|
||||
Assertions.assertEquals("demo in batch", parent.children?.get(1)?.grandChild?.content)
|
||||
Assertions.assertEquals("demo in batch", parent.children?.get(2)?.grandChild?.content)
|
||||
|
||||
// 读写私有内部类类型的成员(使用类型名引用内部类时,无需带外部类名)
|
||||
Assertions.assertEquals("", OmniAccessor.getFirst(parent, "subChild/secret"))
|
||||
OmniAccessor.set(parent, "{InnerChild}/secret", "inner-class secret")
|
||||
Assertions.assertEquals("inner-class secret", OmniAccessor.getFirst(parent, "subChild/secret"))
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.alibaba.testable.demo
|
||||
package com.alibaba.demo.basic
|
||||
|
||||
import com.alibaba.testable.core.accessor.PrivateAccessor
|
||||
import com.alibaba.testable.core.tool.PrivateAccessor
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@@ -8,35 +8,37 @@ import org.junit.jupiter.api.Test
|
||||
* 演示私有成员访问功能
|
||||
* Demonstrate private member access functionality
|
||||
*/
|
||||
internal class DemoPrivateAccessTest {
|
||||
internal class DemoPrivateAccessorTest {
|
||||
|
||||
private val demoPrivateAccess = DemoPrivateAccess()
|
||||
|
||||
@Test
|
||||
fun should_able_to_access_private_method() {
|
||||
assertEquals("hello - 1", PrivateAccessor.invoke(demoPrivateAccess, "privateFunc", "hello", 1))
|
||||
fun should_access_private_method() {
|
||||
val list = listOf("a", "b", "c");
|
||||
assertEquals("abc + hello + 1", PrivateAccessor.invoke(demoPrivateAccess, "privateFunc", list, "hello", 1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_able_to_access_private_field() {
|
||||
fun should_access_private_field() {
|
||||
PrivateAccessor.set(demoPrivateAccess, "count", 3)
|
||||
assertEquals(3, PrivateAccessor.get(demoPrivateAccess, "count"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_able_to_access_private_static_method() {
|
||||
fun should_access_private_static_method() {
|
||||
val list = listOf("a", "b", "c");
|
||||
assertEquals("hello + 1", PrivateAccessor.invokeStatic(DemoPrivateAccess::class.java, "privateStaticFunc", "hello", 1))
|
||||
assertEquals("hello * 1", PrivateAccessor.invokeStatic(DemoPrivateAccess::class.java, "privateJvmStaticFunc", "hello", 1))
|
||||
assertEquals("abc * hello * 1", PrivateAccessor.invokeStatic(DemoPrivateAccess::class.java, "privateJvmStaticFunc", list, "hello", 1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_able_to_access_private_static_field() {
|
||||
fun should_access_private_static_field() {
|
||||
PrivateAccessor.setStatic(DemoPrivateAccess::class.java, "staticCount", 3)
|
||||
assertEquals(3, PrivateAccessor.getStatic(DemoPrivateAccess::class.java, "staticCount"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_able_to_update_final_field() {
|
||||
fun should_update_final_field() {
|
||||
PrivateAccessor.set(demoPrivateAccess, "pi", 4.13)
|
||||
assertEquals(4.13, demoPrivateAccess.pi)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.alibaba.demo.basic
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockConstructor
|
||||
import com.alibaba.testable.core.annotation.MockMethod
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* 演示模板方法的Mock场景
|
||||
* Demonstrate scenario of mocking template method
|
||||
*/
|
||||
internal class DemoTemplateTest {
|
||||
|
||||
private val demoTemplate = DemoTemplate()
|
||||
|
||||
class Mock {
|
||||
@MockMethod
|
||||
private fun <T> getList(self: DemoTemplate, value: T): List<T> {
|
||||
return mutableListOf((value.toString() + "_mock_list") as T)
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
private fun <K, V> getMap(self: DemoTemplate, key: K, value: V): Map<K, V> {
|
||||
return mutableMapOf(key to (value.toString() + "_mock_map") as V)
|
||||
}
|
||||
|
||||
@MockConstructor
|
||||
private fun newHashSet(): HashSet<*> {
|
||||
val set = HashSet<Any>()
|
||||
set.add("insert_mock")
|
||||
return set
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
private fun <E> add(s: MutableSet<E>, e: E): Boolean {
|
||||
s.add((e.toString() + "_mocked") as E)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_mock_single_template_method() {
|
||||
val res = demoTemplate.singleTemplateMethod()
|
||||
Assertions.assertEquals("demo_mock_list", res)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_mock_double_template_method() {
|
||||
val res = demoTemplate.doubleTemplateMethod()
|
||||
Assertions.assertEquals("testable_mock_map", res)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_mock_new_template_method() {
|
||||
val res = demoTemplate.newTemplateMethod()
|
||||
Assertions.assertEquals(2, res.size)
|
||||
val iterator = res.stream().iterator()
|
||||
Assertions.assertEquals("insert_mock", iterator.next())
|
||||
Assertions.assertEquals("world_mocked", iterator.next())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.alibaba.demo.java2kotlin
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockMethod
|
||||
import com.alibaba.testable.core.matcher.InvokeVerifier.verify
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.io.File
|
||||
|
||||
class PathDemoTest {
|
||||
|
||||
class Mock {
|
||||
@MockMethod
|
||||
fun exists(f: File): Boolean {
|
||||
return when (f.absolutePath) {
|
||||
"/a/b" -> true
|
||||
"/a/b/c" -> true
|
||||
else -> f.exists()
|
||||
}
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
fun isDirectory(f: File): Boolean {
|
||||
return when (f.absolutePath) {
|
||||
"/a/b/c" -> true
|
||||
else -> f.isDirectory
|
||||
}
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
fun delete(f: File): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
fun listFiles(f: File): Array<File>? {
|
||||
return when (f.absolutePath) {
|
||||
"/a/b" -> arrayOf(File("/a/b/c"), File("/a/b/d"))
|
||||
"/a/b/c" -> arrayOf(File("/a/b/c/e"))
|
||||
else -> f.listFiles()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_mock_java_method_invoke_in_kotlin() {
|
||||
PathDemo.deleteRecursively(File("/a/b/"))
|
||||
verify("listFiles").withTimes(2)
|
||||
verify("delete").withTimes(4)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.alibaba.demo.one2multi
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockMethod
|
||||
|
||||
class ASvcMock {
|
||||
|
||||
@MockMethod(targetClass = String::class, targetMethod = "format")
|
||||
fun a_format(format: String, vararg args: Any?): String {
|
||||
return "a_mock"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.alibaba.demo.one2multi
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockMethod
|
||||
|
||||
class BSvcMock {
|
||||
|
||||
@MockMethod(targetClass = String::class, targetMethod = "format")
|
||||
fun b_format(format: String, vararg args: Any?): String {
|
||||
return "b_mock"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.alibaba.demo.one2multi
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockMethod
|
||||
|
||||
class CSvcMock {
|
||||
|
||||
@MockMethod(targetClass = String::class, targetMethod = "format")
|
||||
fun c_format(format: String, vararg args: Any?): String {
|
||||
return "c_mock"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.alibaba.demo.one2multi
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockWith
|
||||
import com.alibaba.testable.core.matcher.InvokeVerifier.verify
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@MockWith
|
||||
class OneToMultiSvcTest {
|
||||
|
||||
private val aSvc = ASvc()
|
||||
private val bSvc = BSvc()
|
||||
private val cSvc = CSvc()
|
||||
|
||||
@Test
|
||||
fun should_test_multi_class_together() {
|
||||
Assertions.assertEquals("a_mock", aSvc.demo("test"))
|
||||
Assertions.assertEquals("b_mock", bSvc.demo("test"))
|
||||
Assertions.assertEquals("c_mock", cSvc.demo("test"))
|
||||
verify("a_format").withTimes(1)
|
||||
verify("b_format").withTimes(1)
|
||||
verify("c_format").withTimes(1)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
package com.alibaba.testable.demo
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockMethod
|
||||
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
|
||||
|
||||
/**
|
||||
* 演示父类变量引用子类对象时的Mock场景
|
||||
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
|
||||
*/
|
||||
internal class DemoInheritTest {
|
||||
|
||||
private val demoInherit = DemoInherit()
|
||||
|
||||
@MockMethod(targetMethod = "put")
|
||||
private fun put_into_box(self: Box, something: String) {
|
||||
self.put("put_" + something + "_into_box")
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "put")
|
||||
private fun put_into_blackbox(self: BlackBox, something: String) {
|
||||
self.put("put_" + something + "_into_blackbox")
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "get")
|
||||
private fun get_from_box(self: Box): String {
|
||||
return "get_from_box"
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "get")
|
||||
private fun get_from_blackbox(self: BlackBox): String {
|
||||
return "get_from_blackbox"
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "getColor")
|
||||
private fun get_color_from_color(self: Color): String {
|
||||
return "color_from_color"
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "getColor")
|
||||
private fun get_color_from_blackbox(self: BlackBox): String {
|
||||
return "color_from_blackbox"
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun should_able_to_mock_call_sub_object_method_by_parent_object() {
|
||||
val box = demoInherit.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 = demoInherit.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 = demoInherit.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 = demoInherit.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 = demoInherit.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 = demoInherit.colorViaBox
|
||||
InvokeVerifier.verify("get_color_from_blackbox").withTimes(1)
|
||||
Assertions.assertEquals("color_from_blackbox", color)
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package com.alibaba.testable.demo
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockConstructor
|
||||
import com.alibaba.testable.core.annotation.MockMethod
|
||||
import com.alibaba.testable.core.matcher.InvokeVerifier.verify
|
||||
import com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD
|
||||
import com.alibaba.testable.core.tool.TestableTool.TEST_CASE
|
||||
import com.alibaba.testable.demo.model.BlackBox
|
||||
import com.alibaba.testable.demo.model.ColorBox
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
/**
|
||||
* 演示基本的Mock功能
|
||||
* Demonstrate basic mock functionality
|
||||
*/
|
||||
internal class DemoMockTest {
|
||||
|
||||
private val demoMock = DemoMock()
|
||||
|
||||
@MockConstructor
|
||||
private fun createBlackBox(text: String) = BlackBox("mock_$text")
|
||||
|
||||
@MockMethod
|
||||
private fun innerFunc(self: DemoMock, text: String) = "mock_$text"
|
||||
|
||||
@MockMethod
|
||||
private fun trim(self: BlackBox) = "trim_string"
|
||||
|
||||
@MockMethod(targetMethod = "substring")
|
||||
private fun sub(self: BlackBox, i: Int, j: Int) = "sub_string"
|
||||
|
||||
@MockMethod
|
||||
private fun startsWith(self: BlackBox, s: String) = false
|
||||
|
||||
@MockMethod
|
||||
private fun secretBox(ignore: BlackBox): BlackBox {
|
||||
return BlackBox("not_secret_box")
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
private fun createBox(ignore: ColorBox, color: String, box: BlackBox): BlackBox {
|
||||
return BlackBox("White_${box.get()}")
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
private fun callFromDifferentMethod(self: DemoMock): String {
|
||||
return if (TEST_CASE == "should_able_to_get_test_case_name") {
|
||||
"mock_special"
|
||||
} else {
|
||||
when (SOURCE_METHOD) {
|
||||
"callerOne" -> "mock_one"
|
||||
else -> "mock_others"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun should_able_to_mock_new_object() {
|
||||
assertEquals("mock_something", demoMock.newFunc())
|
||||
verify("createBlackBox").with("something")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_able_to_mock_member_method() {
|
||||
assertEquals("{ \"res\": \"mock_hello\"}", demoMock.outerFunc("hello"))
|
||||
verify("innerFunc").with("hello")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_able_to_mock_common_method() {
|
||||
assertEquals("trim_string__sub_string__false", demoMock.commonFunc())
|
||||
verify("trim").withTimes(1)
|
||||
verify("sub").withTimes(1)
|
||||
verify("startsWith").withTimes(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_able_to_mock_static_method() {
|
||||
assertEquals("White_not_secret_box", demoMock.getBox().get())
|
||||
verify("secretBox").withTimes(1)
|
||||
verify("createBox").withTimes(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_able_to_get_source_method_name() {
|
||||
// synchronous
|
||||
assertEquals("mock_one_mock_others", demoMock.callerOne() + "_" + demoMock.callerTwo())
|
||||
// asynchronous
|
||||
assertEquals("mock_one_mock_others", Executors.newSingleThreadExecutor().submit<String> {
|
||||
demoMock.callerOne() + "_" + demoMock.callerTwo()
|
||||
}.get())
|
||||
verify("callFromDifferentMethod").withTimes(4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_able_to_get_test_case_name() {
|
||||
// synchronous
|
||||
assertEquals("mock_special", demoMock.callerOne())
|
||||
// asynchronous
|
||||
assertEquals("mock_special", Executors.newSingleThreadExecutor().submit<String> {
|
||||
demoMock.callerOne()
|
||||
}.get())
|
||||
verify("callFromDifferentMethod").withTimes(2)
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package com.alibaba.testable.demo
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockConstructor
|
||||
import com.alibaba.testable.core.annotation.MockMethod
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* 演示模板方法可以被Mock
|
||||
* Demonstrate template method can be mocked
|
||||
*/
|
||||
internal class DemoTemplateTest {
|
||||
|
||||
private val demoTemplate = DemoTemplate()
|
||||
|
||||
@MockMethod
|
||||
private fun <T> getList(self: DemoTemplate, value: T): List<T> {
|
||||
return mutableListOf((value.toString() + "_mock_list") as T)
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
private fun <K, V> getMap(self: DemoTemplate, key: K, value: V): Map<K, V> {
|
||||
return mutableMapOf(key to (value.toString() + "_mock_map") as V)
|
||||
}
|
||||
|
||||
@MockConstructor
|
||||
private fun newHashSet(): HashSet<*> {
|
||||
val set = HashSet<Any>()
|
||||
set.add("insert_mock")
|
||||
return set
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
private fun <E> add(s: MutableSet<E>, e: E): Boolean {
|
||||
s.add((e.toString() + "_mocked") as E)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun should_able_to_mock_single_template_method() {
|
||||
val res = demoTemplate.singleTemplateMethod()
|
||||
Assertions.assertEquals("demo_mock_list", res)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_able_to_mock_double_template_method() {
|
||||
val res = demoTemplate.doubleTemplateMethod()
|
||||
Assertions.assertEquals("testable_mock_map", res)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_able_to_mock_new_template_method() {
|
||||
val res = demoTemplate.newTemplateMethod()
|
||||
Assertions.assertEquals(2, res.size)
|
||||
val iterator = res.stream().iterator()
|
||||
Assertions.assertEquals("insert_mock", iterator.next())
|
||||
Assertions.assertEquals("world_mocked", iterator.next())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package com.alibaba.testable.demo.util
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockMethod
|
||||
import com.alibaba.testable.core.matcher.InvokeVerifier.verify
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.io.File
|
||||
|
||||
class PathUtilTest {
|
||||
|
||||
@MockMethod
|
||||
fun exists(f: File): Boolean {
|
||||
return when (f.absolutePath) {
|
||||
"/a/b" -> true
|
||||
"/a/b/c" -> true
|
||||
else -> f.exists()
|
||||
}
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
fun isDirectory(f: File): Boolean {
|
||||
return when (f.absolutePath) {
|
||||
"/a/b/c" -> true
|
||||
else -> f.isDirectory
|
||||
}
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
fun delete(f: File): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
@MockMethod
|
||||
fun listFiles(f: File): Array<File>? {
|
||||
return when (f.absolutePath) {
|
||||
"/a/b" -> arrayOf(File("/a/b/c"), File("/a/b/d"))
|
||||
"/a/b/c" -> arrayOf(File("/a/b/c/e"))
|
||||
else -> f.listFiles()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun should_able_to_mock_java_method_invoke_in_kotlin() {
|
||||
PathUtil.deleteRecursively(File("/a/b/"))
|
||||
verify("listFiles").withTimes(2)
|
||||
verify("delete").withTimes(4)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
<modules>
|
||||
<module>java-demo</module>
|
||||
<module>kotlin-demo</module>
|
||||
<module>spock-demo</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
|
||||
5
demo/settings.gradle
Normal file
5
demo/settings.gradle
Normal file
@@ -0,0 +1,5 @@
|
||||
rootProject.name = 'demos'
|
||||
|
||||
include 'java-demo'
|
||||
include 'kotlin-demo'
|
||||
include 'spock-demo'
|
||||
28
demo/spock-demo/build.gradle
Normal file
28
demo/spock-demo/build.gradle
Normal file
@@ -0,0 +1,28 @@
|
||||
plugins {
|
||||
id 'groovy'
|
||||
}
|
||||
|
||||
group = 'com.alibaba.testable'
|
||||
version = '1.0.0-SNAPSHOT'
|
||||
sourceCompatibility = '8'
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'org.codehaus.groovy:groovy-all:3.0.7'
|
||||
testImplementation 'org.spockframework:spock-core:2.0-M5-groovy-3.0'
|
||||
testImplementation('com.alibaba.testable:testable-all:0.6.1')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.6.1')
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
test {
|
||||
jvmArgs "-javaagent:${classpath.find { it.name.contains("testable-agent") }.absolutePath}"
|
||||
useJUnitPlatform()
|
||||
}
|
||||
86
demo/spock-demo/pom.xml
Normal file
86
demo/spock-demo/pom.xml
Normal file
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Refer to: https://github.com/spockframework/spock-example/blob/master/pom.xml -->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>spock-demo</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<name>spock-demo</name>
|
||||
<description>Spock demo for Testable</description>
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<testable.version>0.6.1</testable.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-bom</artifactId>
|
||||
<version>2.0-M5-groovy-3.0</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy</artifactId>
|
||||
<version>3.0.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-junit4</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-all</artifactId>
|
||||
<version>${testable.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<!-- The gmavenplus plugin is used to compile Groovy code -->
|
||||
<groupId>org.codehaus.gmavenplus</groupId>
|
||||
<artifactId>gmavenplus-plugin</artifactId>
|
||||
<version>1.12.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
<goal>compileTests</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0-M5</version>
|
||||
<configuration>
|
||||
<argLine>-javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
|
||||
<useFile>false</useFile>
|
||||
<includes>
|
||||
<include>**/*Test.java</include>
|
||||
<include>**/*Spec.java</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.github.pbetkier.spockdemo;
|
||||
|
||||
import com.github.pbetkier.spockdemo.model.SpockBox;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DemoSpock {
|
||||
|
||||
public SpockBox createBoxOfNum() {
|
||||
SpockBox box = new SpockBox();
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
box.put(String.valueOf(i));
|
||||
}
|
||||
return box;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.github.pbetkier.spockdemo.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SpockBox {
|
||||
|
||||
private List<String> contents = new ArrayList<>();
|
||||
|
||||
public int size() {
|
||||
return contents.size();
|
||||
}
|
||||
|
||||
public void put(String data) {
|
||||
contents.add(data);
|
||||
}
|
||||
|
||||
public String pop() {
|
||||
if (contents.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String data = contents.get(size() - 1);
|
||||
contents.remove(size() - 1);
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.github.pbetkier.spockdemo
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockConstructor
|
||||
import com.alibaba.testable.core.annotation.MockMethod
|
||||
import com.github.pbetkier.spockdemo.model.SpockBox
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Specification
|
||||
|
||||
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
|
||||
|
||||
class DemoSpockTest extends Specification {
|
||||
|
||||
@Shared
|
||||
def demoSpock = new DemoSpock()
|
||||
|
||||
static class Mock {
|
||||
@MockConstructor
|
||||
SpockBox createBox() {
|
||||
SpockBox box = new SpockBox()
|
||||
box.put("mock zero")
|
||||
return box
|
||||
}
|
||||
|
||||
@MockMethod(targetMethod = "put")
|
||||
void putBox(SpockBox self, String data) {
|
||||
self.put("mock " + data)
|
||||
}
|
||||
}
|
||||
|
||||
def "should get a box of numbers"() {
|
||||
given:
|
||||
def box = demoSpock.createBoxOfNum()
|
||||
|
||||
expect:
|
||||
box.size() == 4
|
||||
box.pop() == "mock 3"
|
||||
box.pop() == "mock 2"
|
||||
box.pop() == "mock 1"
|
||||
box.pop() == "mock zero"
|
||||
verify("createBox").withTimes(1)
|
||||
verify("putBox").withInOrder("1").withInOrder("2").withInOrder("3")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
TestableMock Introduction
|
||||
---
|
||||
|
||||
The Mock method in unit testing is usually to bypass method calls that rely on external resources or irrelevant functions, so that the focus of the test can be keep on the code logic that needs to be verified and guaranteed.
|
||||
The mock method in unit testing is usually to bypass method calls that rely on external resources or irrelevant functions, so that the focus of the test can be keep on the code logic that needs to be verified and guaranteed.
|
||||
|
||||
When defining the Mock method, the developer really cares about only one thing: "<u>This call should be replaced with the fake **mock method** during testing</u>".
|
||||
When defining the mock method, developers really care about only one thing: "<u>This call should be replaced with the fake **mock method** during testing</u>".
|
||||
|
||||
However, when the current mainstream Mock framework implements the Mock function, developers have to worry about too many things: how the Mock framework is initialized, whether it is compatible with the unit testing framework used, whether the method to be mocked is private or static, whether the Mock object is created by `new` operator or injected, how to send the mock object back to the class under test... These non-critical additional tasks greatly distract the fun of using the mock tool.
|
||||
However, when the current mainstream mock framework implements the mock function, developers have to worry about too many things: how the mock framework is initialized, whether it is compatible with the unit testing framework used, whether the method to be mocked is private or static, whether the object to be mocked is created by `new` operator or injected, how to send the mock object back to the class under test... These non-critical additional tasks greatly distract the fun of using the mock tool.
|
||||
|
||||
Therefore, we developed `TestableMock`, **a maverick and lightweight mock tool**.
|
||||
|
||||
|
||||
9
docs/en-us/doc/about-us.md
Normal file
9
docs/en-us/doc/about-us.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## About Us
|
||||
|
||||
The core development team of `TestableMock` comes from **Alibaba Cloud · Cloud R&D Department** (formerly known as **Alibaba Group R&D Efficiency Department**). We not only internally support Alibaba Group's end-to-end project code, testing, and release process of thousands of BU-level product lines, but also the product R&D team of Alibaba Cloud enterprise-level R&D collaboration platform [云效](https://www.aliyun.com/product/yunxiao). As has been deeply involved in the field of developer tools, we are committed to improving the production experience of developers and building a digital R&D ecosystem for enterprises.
|
||||
|
||||
The function of this project is inspired by our summary of the pain points that Java developers often encounter in daily unit testing ([external dependence on Mock is cumbersome](en-us/doc/use-mock.md), [private method is difficult to test](en-us/doc/private-accessor.md), [void type method is difficult to test](en-us/doc/test-void-method.md), and [complex parameters are difficult to construct](en-us/doc/parameter-constructor.md)). Its internal name is `Testable`, and was renamed `TestableMock` for SEO reason when it was open sourced, thus the lightweight mock function is highlighted. Don't be fooled by its name, `TestableMock` is more than just a mocking tool.
|
||||
|
||||
From the incubation of hackathon, to internal open source, and then to external open source, `TestableMock` has accumulated a group of developer users in the internal and external communities. At the same time, we are constantly improving the functional richness and stability of `TestableMock` itself. According to the current version release process, we usually update the internal beta version containing the `SNAPSHOT` mark internally, and release the official version to the Maven central repository (except for the severe bug fix version) after at least one day of stable used to ensure the update will bring more convenience and less burden to everyone.
|
||||
|
||||
Finally, we are relatively optimistic about the discussion in the community about "whether too powerful test assistance is equivalent to condoning code corruption". Just as the birth of `PowerMock` did not really spawn more bad projects, but actually solved many test problems left over by the Java language. Rather than use "code tricks" to test functions indirectly, it could be worth to just remove the constraints, and let the unit test break in all its fury! 🤠
|
||||
4
docs/en-us/doc/annotations.md
Normal file
4
docs/en-us/doc/annotations.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Available Annotations
|
||||
---
|
||||
|
||||
TO BE TRANSLATED
|
||||
@@ -1,9 +1,9 @@
|
||||
Mock Tools Comparison
|
||||
---
|
||||
|
||||
Besides `TestableMock`, there are also several other community Mock tools, such as `Mockito`, `Spock`, `PowerMock` and `JMockit`. Comparison as follows:
|
||||
Besides `TestableMock`, there are also several other community mock tools, such as `Mockito`, `Spock`, `PowerMock` and `JMockit`. Comparison as follows:
|
||||
|
||||
| Tool | Mechanism | Minimal Mock Unit | Limitation of method be mocked | Ease of use | IDE support |
|
||||
| Tool | Mechanism | Minimal mock unit | Limitation of method be mocked | Ease of use | IDE support |
|
||||
| ---- | ---- | ---- | ---- | ---- | ---- |
|
||||
| Mockito | Dynamic proxy | Class | Except private/static method and constructor | **Easy** | **Very well** |
|
||||
| Spock | Dynamic proxy | Class | Except private/static method and constructor | Complicate | Just so so |
|
||||
@@ -23,4 +23,4 @@ The functionality of `TestabledMock` is basically the same as that of `PowerMock
|
||||
|
||||
The main disadvantage of the current `TestableMock` is that the IDE cannot promptly prompt whether the method parameters are matched correctly when writing the mock method. If the mocking effect does not meet expectation, it has to be verified during runtime through the method provided in the [self-help troubleshooting](en-us/doc/troubleshooting.md) document. This feature needs to be provided by extending IDE plugins in the future.
|
||||
|
||||
In addition, because `TestableMock` uses the mock mechanism based on a single method in a unique way, it decouples the mock method definition from the unit test cases. Thus, the mock methods are by default reusable, and the unit test cases become cleaner and purer. On the other hand, the mock methods become fragmented, and life cycle management is relatively difficult. Therefore, it may take some time for developers get used to it.
|
||||
In addition, because `TestableMock` uses the mechanism based on "each source class binding to its own mock class" in a unique way, it decouples the mock method definition from the unit test cases. Thus, the mock methods are by default reusable, and the unit test cases become cleaner and purer. On the other hand, the mock methods become fragmented, and life cycle management is relatively difficult. Therefore, it may take some time for developers get used to it.
|
||||
|
||||
29
docs/en-us/doc/design-and-mechanism.md
Normal file
29
docs/en-us/doc/design-and-mechanism.md
Normal file
@@ -0,0 +1,29 @@
|
||||
How Mock Works
|
||||
---
|
||||
|
||||
This document mainly introduces the design and implementation principles of the mock function in `TestableMock`.
|
||||
|
||||
Unlike common mock-tools which write mock definitions in each test case, `TestableMock` allows each business class to provide its own set of mock methods, describing what should be mocked during testing, and define the corresponding replacement logic (that is, each business class has its own independent "Test class" and independent "Mock class"). By adopting "convention is better than configuration" principle, `TestableMock` not only reduce redundant codes but also reduce the cost of mock learning.
|
||||
|
||||
This design is based on two basic assumptions:
|
||||
|
||||
1. In any business class, methods that need to be mocked in one test case are usually required to be mocked in other test cases. For these mocked methods usually requiring external dependencies that are not easy to test.
|
||||
2. Each unit test only focuses on the logic inside the unit under test, and irrelevant calls outside the unit should be replaced with mocks. That is, the invocations that need to be mocked should all be in the code of the class under test.
|
||||
|
||||
Accordingly, the unit test scenarios that meet the above assumptions are simplified through conventions, and the remaining more complex use scenarios are supported through configuration.
|
||||
|
||||
The mechanism of `TestableMock` can be summarized in one sentence: <u>Using java agent to dynamically modify the bytecode, before the unit test is about to run, replace all invocations in the business class under test which match the mock method definition with invocations to the mock method itself.</u>.
|
||||
|
||||
The final effect is that no matter what service framework or object-container the code uses, no matter whether the object of mock target is injected by a framework, created by new operation, and whether the target method of mock is private or external, defined as global, local, static, inherited or overloaded, all can be mocked in a same and simple way, which make unit testing much easier.
|
||||
|
||||
> Notice: Mock's goal is the method invocation in the class under test. The code inside the test case will not be mocked, and the method definition itself has not changed, but the invocation code to those methods will be replaced.
|
||||
|
||||
Specifically, when the unit test is started, `TestableMock` will preprocess the classes loaded into memory and establish the association relationship between the "class under test", the "test class", and the "mock container class" respectively (can be one-to-one or many-to-one). On the one hand, this association is to correctly match the mock call and replace it before the test case is executed, and on the other hand, it is used to control the effective scope of the mock method.
|
||||
|
||||
For the class under test, replace the matched call with a call to the mock container method.
|
||||
|
||||
For the test class, insert the mock context initialization code at the beginning of each test case.
|
||||
|
||||
For the mock container class, add the `testableIns()` method to make the class become a singleton class, and insert codes to record the call at the beginning of each Mock method.
|
||||
|
||||
The above is the core logic of the entire mocking logic. For more implementation details, please refer to the source code. If you have any questions, suggestions, or improvement proposals, you are welcome to participate in the discussion and contribute through Github Issue and Pull Request 😃
|
||||
@@ -1,8 +1,8 @@
|
||||
Feedback Channel
|
||||
---
|
||||
|
||||
If you have any questions or suggestions during the use of `TestableMock`, please go ahead raise them in [Project Issue](https://github.com/alibaba/testable-mock/issues) and we will usually reply within 24 hours.
|
||||
If you have any questions or suggestions during the use of `TestableMock`, please go ahead raise them at [Github Issues](https://github.com/alibaba/testable-mock/issues) and we will usually reply within 24 hours.
|
||||
|
||||
Please describe the situation in detail. If you think the problem may be related to the potential BUG of `TestableMock`, please try to provide reproducible source code or examples for shorten the troubleshooting time.
|
||||
Description of the situation you meet in detail is highly welcomed. If the problem may be related to a potential BUG of `TestableMock`, please try to provide reproducible source code or examples for shorten the troubleshooting time.
|
||||
|
||||
`TestableMock`, with no more untestable code in Java : )
|
||||
|
||||
@@ -5,44 +5,58 @@ Frequently Asked Questions
|
||||
|
||||
Create the class under test object directly, and then use the ability of `TestableMock` to access private members to directly assign values to these fields.
|
||||
|
||||
#### 2. Can `TestableMock` be used with other Mock tools?
|
||||
#### 2. Can `TestableMock` be used with other mock tools?
|
||||
|
||||
`TestableMock` can be safely used together with other Mock tools based on dynamic proxy mechanism, such as `Mockito`, `EasyMock`, `Spock`, etc., all belong to this category.
|
||||
`TestableMock` can be safely used together with other mock tools based on dynamic proxy mechanism, such as `Mockito`, `EasyMock`, `Spock`, etc., all belong to this category.
|
||||
|
||||
For Mock tools that modify the class loader or the bytecode of the class under test, such as `PowerMock` and `JMockit`, there is no yet case to prove that they will conflict with `TestableMock`, but in principle, there may be a risk of incompatibility between the two, Please use with caution.
|
||||
For mock tools that modify the class loader or the bytecode of the class under test, such as `PowerMock` and `JMockit`, there is no yet case to prove that they will conflict with `TestableMock`, but in principle, there may be a risk of incompatibility between the two, Please use with caution.
|
||||
|
||||
#### 3. How to implement the mock method when the parent class variable points to the child class object?
|
||||
#### 3. Which test frameworks does `TestableMock` support?
|
||||
|
||||
The `PrivateAccessor` / `OmniConstructor` / `OmniAccessor` and basic functionality of `TestableMock` are theoretically test-frameworks-independent, and should work with any test frameworks out-of-box.
|
||||
|
||||
The only test-framework-related part is the mock invocation verifier, which currently supports `JUnit 4` / `JUnit 5` / `TestNG` and `Spock` appropriately.
|
||||
|
||||
If you encounter compatibility problem under a specific test framework, or want to add mock invocation verifier support for any other frameworks, please raise an [issue](https://github.com/alibaba/testable-mock/issues) to tell us.
|
||||
|
||||
#### 4. How to implement the mock method when the parent class variable points to the child class object?
|
||||
|
||||
In the code, there are often cases of using <u>interface variables or parent class variables</u> to point to an instance of subclasses and calling methods provided by the parent class or subclass.
|
||||
|
||||
At this time, follow a principle that the type of the first parameter of the mock method is always the same as the type of the variable that initiated the call.
|
||||
|
||||
Therefore, regardless of whether the actually called method comes from the parent class or the subclass, and whether the subclass overrides the method. If the calling variable is of the parent type (or interface type), the first parameter type of the Mock method should use the corresponding parent type (or interface) type.
|
||||
Therefore, regardless of whether the actually called method comes from the parent class or the subclass, and whether the subclass overrides the method. If the calling variable is of the parent type (or interface type), the first parameter type of the mock method should use the corresponding parent type (or interface) type.
|
||||
|
||||
See the use case of the `DemoInheritTest` test class in the Java and Kotlin examples.
|
||||
|
||||
#### 4. How to mock generic methods (template methods)?
|
||||
#### 5. How to mock generic methods (template methods)?
|
||||
|
||||
Same as the Mock method of the ordinary method, just use the same generic parameters directly on the Mock method.
|
||||
Just use the same generic parameters directly on the mock method.
|
||||
|
||||
See the use case of the `DemoTemplateTest` test class in the Java and Kotlin examples.
|
||||
|
||||
> Because JVM has a generic erasure mechanism, you can also directly use the `Object` type to replace generic parameters for Java projects, see the commented out "Second solution" example in the Java version of the `DemoTemplateTest` test class.
|
||||
|
||||
#### 5. Why mocking methods in the `String` class in the Kotlin project does not work?
|
||||
#### 6. How to mock invocation inside an inner class?
|
||||
|
||||
The `String` type in Kotlin language is actually `kotlin.String` instead of `java.lang.String`. However, when this type is built from bytecode, it will be replaced with Java's `java.lang.String` class, so no matter if the Mock target is written as `kotlin.String` or `java.lang.String`, it cannot match the original called method.
|
||||
Just put mock methods in the mock container class of its outer class, it works for code in all inner classes.
|
||||
|
||||
See the use case of the `DemoInnerClass` test class in the Java and Kotlin examples.
|
||||
|
||||
#### 7. Why mocking methods in the `String` class in the Kotlin project does not work?
|
||||
|
||||
The `String` type in Kotlin language is actually `kotlin.String` instead of `java.lang.String`. However, when this type is built from bytecode, it will be replaced with Java's `java.lang.String` class, so no matter if the mock target is written as `kotlin.String` or `java.lang.String`, it cannot match the original called method.
|
||||
|
||||
In actual scenarios, there are very few scenarios where methods in the `String` class need to be mocked, so `TestableMock` has not dealt with this situation specifically.
|
||||
|
||||
#### 6. When trigger a single test case in IntelliJ IDE, why class with `@EnablePrivateAccess` annotation report still private member access errors?
|
||||
|
||||
The default compiler provided by IntelliJ handle the annotation processor of the `JSR-269` specification in an incompatible way of maven. You can turn on the "Delegate IDE build/run actions to maven" option in "Build Tools > Maven > Runner" of IntelliJ system configuration:
|
||||
|
||||

|
||||
|
||||
#### 7. Can `TestableMock` be used for testing Android projects?
|
||||
#### 8. Can `TestableMock` be used for testing Android projects?
|
||||
|
||||
It can be used in combination with [Roboelectric](https://github.com/robolectric/robolectric) testing framework.
|
||||
|
||||
The `Dalvik` and `ART` virtual machines of the Android system use a bytecode system different from the standard JVM, which will affect the normal functionality of `TestableMock`. The `Roboelectric` framework can run Android unit tests on a standard JVM virtual machine, which is much faster than running unit tests through the Android virtual machine. Recently, most Android App unit tests are written with the `Roboelectric` framework.
|
||||
|
||||
#### 9. Meet "Command Line is too Long. Shorten command line for ..." error when triggering test in IntelliJ IDE?
|
||||
|
||||
This problem is caused by the system `Class Path` content is too long, and has nothing to do with `TestableMock`. However, it should be noted that IntelliJ provides two auxiliary solutions: `JAR manifest` and `classpath file`. If `TestableMock` is used in the test, please select `JAR manifest`.
|
||||
|
||||

|
||||
|
||||
4
docs/en-us/doc/javaagent-args.md
Normal file
4
docs/en-us/doc/javaagent-args.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Available Global Arguments
|
||||
---
|
||||
|
||||
TO BE TRANSLATED
|
||||
4
docs/en-us/doc/mock-method-reusing.md
Normal file
4
docs/en-us/doc/mock-method-reusing.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Reusing Mock Methods
|
||||
---
|
||||
|
||||
TO BE TRANSLATED
|
||||
113
docs/en-us/doc/omni-constructor.md
Normal file
113
docs/en-us/doc/omni-constructor.md
Normal file
@@ -0,0 +1,113 @@
|
||||
Parameter Constructor
|
||||
---
|
||||
|
||||
In unit testing, the preparation and construction of test data is a necessary and tedious task. Object-oriented layer-by-layer encapsulation becomes an obstacle to initializing the state of the object during testing. Especially when the type structure is complicated, there is no suitable construction method, or some fields need to use private inner classes, etc. Using conventional methods to construct those class often appear to be inadequate.
|
||||
|
||||
For this reason, `TestableMock` provides two minimalist tool classes, `OmniConstructor` and `OmniAccessor`, which makes the construction of any object no longer difficult.
|
||||
|
||||
### 1. Construct any object with one line of code
|
||||
|
||||
No matter how special the target type is, `OmniConstructor` will hand it to you immediately~~ The universal object constructor `OmniConstructor` has two static methods:
|
||||
|
||||
- `newInstance(<AnyClass>)` ➜ Specify any type, and return an object of that type
|
||||
- `newArray(<AnyClass>, <ArraySize>)` ➜ Specify any type, and return an array of that type
|
||||
|
||||
Usage example:
|
||||
|
||||
```java
|
||||
// Construct a object of "WhatEverClass" type
|
||||
WhatEverClass obj = OmniConstructor.newInstance(WhatEverClass.class);
|
||||
// Construct a array of "WhatEverClass[]" type with capability of 5
|
||||
WhatEverClass[] arr = OmniConstructor.newArray(WhatEverClass.class, 5);
|
||||
```
|
||||
|
||||
Beside that, object constructed by `OmniConstructor` is not just a simple empty object with all member values of `null`, but a "fullness" object in which all members and all sub-members of all members have been recursively initialized. Compared with using `new` operation, `OmniConstructor` can ensure the integrity of the object structure and avoid the `NullPointerException` problem caused by partial initialization of test data.
|
||||
|
||||
```java
|
||||
// Construct object using new operation
|
||||
Parent parent = new Parent();
|
||||
// Inner member is not initialized, will cause NullPointerException (❌)
|
||||
parent.getChild().getGrandChild();
|
||||
|
||||
// Construct object using OmniConstructor
|
||||
Parent parent = OmniConstructor.newInstance(Parent.class);
|
||||
// No need to worry, visit any child member safely (✅)
|
||||
parent.getChild().getGrandChild().getContent();
|
||||
```
|
||||
|
||||
> **Note**: In the `0.6.0` version, the member fields of type interface or abstract class will still be initialized to `null`, this problem will be fixed in subsequent versions
|
||||
|
||||
In addition to use as input parameters of the method under test, `OmniConstructor` can also be used to quickly construct the return value of the mock method. Compared to using `null` as the return value of the mock method, using a fully initialized object can better guarantee the reliability of the test .
|
||||
|
||||
In the `DemoOmniMethodsTest` test class of the `java-demo` and `kotlin-demo` sample projects, it is shown in detail how `OmniConstructor` could be used when the target type has a multi-layered nested structure, the construction method is throwing exception, and even without public construction method available.
|
||||
|
||||
### 2. Access any inner member with one line of code
|
||||
|
||||
For test data, even with complex structure, it is usually only part of its attributes and states that are related to a specific test case. However, it is sometimes not easy to assign values to these fields deep wrapped in the object structure.
|
||||
|
||||
As an enhanced version of the `PrivateAccessor` tool, `OmniAccessor` is inspired by the [XPath node selector](https://www.w3schools.com/xml/xpath_syntax.asp) in the `XML` language, It provides two main static methods of `get` and `set`:
|
||||
|
||||
- `get(arbitrary object, "access path")` ➜ returns all member objects searched for based on path-matching
|
||||
- `set(arbitrary object, "access path", new value)` ➜ Assign a value to any objects based on path-matching
|
||||
|
||||
There is also a `getFirst()` method used to directly obtain the unique target object during exact path matching. Its function is equivalent to `OmniAccessor.get(...).get(0)`:
|
||||
|
||||
- `getFirst(arbitrary object, "access path")` ➜ returns the first member object searched based on path-matching
|
||||
|
||||
You only need to write the access path that meets the rules, no matter what type and depth of members, you can directly reach them with one line of code:
|
||||
|
||||
```java
|
||||
// Get all field of the parent object, which named as content and inside type GrandChild
|
||||
OmniAccessor.get(parent, "{GrandChild}/content");
|
||||
// Assign 100 to any fields which named as value and inside any child member that matches the 3rd item of the array named children
|
||||
OmniAccessor.set(parent, "children[2]/*/value", 100);
|
||||
```
|
||||
|
||||
The path rules are as follows:
|
||||
|
||||
**1. Matching member name**
|
||||
|
||||
The path name without additional decoration will match any member object with the same name
|
||||
|
||||
- `child`: match any descendant member whose name is `child`
|
||||
- `child/grandChild`: matches the child member named `grandChild` among the descendants of the name `child`
|
||||
|
||||
**2. Matching member type**
|
||||
|
||||
Use curly braces to match the type name, usually used to obtain or assign multiple member objects of the same type in batches
|
||||
|
||||
- `{Child}`: match all descendants of `Child`
|
||||
- `{Children[]}`: match all descendants of the `Children` array
|
||||
- `{Child}/{GrandChild}`: match all descendant members of `Child`, all types are children of `GrandChild`
|
||||
|
||||
The member name and type can be mixed on the path (currently it is not supported to specify the member name and type at the same time in the same level path)
|
||||
|
||||
- `child/{GrandChild}`: match all descendant members whose name is `child`, all types are child members of `GrandChild`
|
||||
- `{Child}/grandChild/content`: match all descendant members whose type is `Child`, the child members named `grandChild`, and the child members named `content`
|
||||
|
||||
**3. Use subscripts to access array members**
|
||||
|
||||
Use square brackets with numerical values to indicate that the matching position is an array type, and the object with the specified subscript is taken (without subscript, when the matching object is an array type, all objects in the array are matched)
|
||||
|
||||
- `children[1]/content`: match the descendant members of the array type named `children`, and take the child member named `content` in the `2`th object
|
||||
- `parent/children[1]`: match the child member of the array type named `children` among the descendant members named `parent`, and take the `2`th object among them
|
||||
|
||||
**4. Use wildcards**
|
||||
|
||||
Wildcards can be used to match member names or type names
|
||||
|
||||
- `child*`: match all descendant members whose name starts with `child`
|
||||
- `{*Child}`: match all descendant members whose type ends with `Child`
|
||||
- `c*ld/{Grand*ld}`: match the descendant members whose name starts with `c` and ends with `ld`, and the members whose type starts with `Grand` and ends with `ld`
|
||||
- `child/*/content`: At this time, `*` will match any member, that is, the child member of `content` contained in any child member of the `child` object
|
||||
|
||||
For details, see the use cases in the test classes of the `java-demo` and `kotlin-demo` sample projects `DemoOmniMethodsTest`.
|
||||
|
||||
### 3. Special instructions
|
||||
|
||||
> **Do you really need to use `OmniAccessor`? **
|
||||
>
|
||||
> `OmniAccessor` implement the basic anti-refactoring mechanism based on the Fail-Fast principle. When the access path provided by the user cannot match any member, the `OmniAccessor` will immediately throw a `NoSuchMemberError` error, so that the unit test is terminated early. However, compared to the conventional member access method, the support of `OmniAccessor` in IDE refactoring is still weak.
|
||||
>
|
||||
> For content assignment of complex objects, in most cases, we recommend using [Builder Pattern](https://www.geeksforgeeks.org/builder-pattern-in-java/), or exposing Getter/Setter method implementations. Although these conventional methods are slightly clumsy (especially when you need to assign values to many similar members in batches), they are more friendly to the encapsulation and reconstruction of business logic.
|
||||
> Only when the original type is not suitable for transformation, and there is no other way to access the target member, `OmniAccessor` is the last resort.
|
||||
@@ -9,26 +9,42 @@ In addition, before unit testing begin, it is often necessary to initialize spec
|
||||
|
||||
Just add `@EnablePrivateAccess` annotation to the test class, then you have got the following enhancements in the test case:
|
||||
|
||||
- Invoke private methods (including static methods) of the class under test
|
||||
- Read private fields (including static fields) of the class under test
|
||||
- Modify private fields (including static fields) of the class under test
|
||||
- Modify the constant fields of the class under test (fields modified with final, including static fields)
|
||||
- Invoke private methods (including static methods) of the **class under test**
|
||||
- Read private fields (including static fields) of the **class under test**
|
||||
- Modify private fields (including static fields) of the **class under test**
|
||||
- Modify the constant fields of the **class under test** (fields modified with final, including static fields)
|
||||
|
||||
When accessing and modifying private and constant members, the IDE may prompt some syntax errors, but the compiler will be able to run the test normally.
|
||||
|
||||
For the effect, see the use case in the test class of the `java-demo` sample project `DemoPrivateAccessTest`. (Using compile-time code enhancement, currently only the adaptation of the Java language is implemented)
|
||||
|
||||
> This function assumes that the test class is in the same package as the class under test, and the name is `<ClassUnderTest>+Test`. When this convention is not met, you can use the `srcClass` parameter on the `@EnablePrivateAccess` annotation to specify the actual class under test. E.g:
|
||||
>
|
||||
> ```java
|
||||
> @EnablePrivateAccess(srcClass = DemoServiceImpl.class)
|
||||
> class DemoServiceTest() { ... }
|
||||
> ```
|
||||
|
||||
### Solution 2: Use the `PrivateAccessor` tool class
|
||||
|
||||
If you don't want to see the IDE's syntax error reminder, or in a non-Java language JVM project (such as Kotlin language), you can also use the `PrivateAccessor` tool class to directly access private members.
|
||||
|
||||
This class provides 6 static methods:
|
||||
This class provides 7 static methods:
|
||||
|
||||
- `PrivateAccessor.get(<ObjectUnderTest>, "<private-field-name>")` ➜ read the private field of the class under test
|
||||
- `PrivateAccessor.set(<ObjectUnderTest>, "<private-field-name>", <new-value>)` ➜ modify the private field (or constant field) of the class under test
|
||||
- `PrivateAccessor.invoke(<ObjectUnderTest>, "<private-method-name>", <call-parameters>..)` ➜ call the private method of the class under test
|
||||
- `PrivateAccessor.getStatic(<ClassUnderTest>, "<private-static-field-name>")` ➜ read the **static** private field of the class under test
|
||||
- `PrivateAccessor.setStatic(<ClassUnderTest>, "<private-static-field-name>", <new-value>)` ➜ modify the **static** private field (or **static** constant field) of the class under test
|
||||
- `PrivateAccessor.invokeStatic(<ClassUnderTest>, "<private-static-method-name>", <call-parameters>..)` ➜ call the **static** private method of the class under test
|
||||
- `PrivateAccessor.get(<AnyObject>, "<private-field-name>")` ➜ read the private field of any object
|
||||
- `PrivateAccessor.set(<AnyObject>, "<private-field-name>", <new-value>)` ➜ modify the private field (or constant field) of any object
|
||||
- `PrivateAccessor.invoke(<AnyObject>, "<private-method-name>", <call-parameters>...)` ➜ call the private method of any object
|
||||
- `PrivateAccessor.getStatic(<AnyClass>, "<private-static-field-name>")` ➜ read the **static** private field of any class
|
||||
- `PrivateAccessor.setStatic(<AnyClass>, "<private-static-field-name>", <new-value>)` ➜ modify the **static** private field (or **static** constant field) of any class
|
||||
- `PrivateAccessor.invokeStatic(<AnyClass>, "<private-static-method-name>", <call-parameters>...)` ➜ call the **static** private method of any class
|
||||
- `PrivateAccessor.construct(<AnyClass>, <constructor-parameters>...)` ➜ create a new object by the private constructor of any class
|
||||
|
||||
> Using the `PrivateAccessor` class does not require the test class to have `@EnablePrivateAccess` annotation, but adding this annotation will enable the compile-time verification for the private members of the class under test.
|
||||
|
||||
For details, see the use cases in the test classes of the `java-demo` and `kotlin-demo` sample projects `DemoPrivateAccessTest`.
|
||||
|
||||
### Compile-time verification of private members
|
||||
|
||||
Both of the above two methods essentially use JVM reflection mechanism to achieve private member access, but the JVM compiler will not check the existence of the reflection target. When the code is refactored, if the private method names and parameters in the source class are modified, it would cause exceptions to be discovered only when the unit test is triggered. For this reason, another function of the `@EnablePrivateAccess` annotation is to perform additional compile-time checks for existence of private member of the **class under test**.
|
||||
|
||||
**Note**: When the private member verification function is enabled, the `PrivateAccessor` class can only be used to access the private members of the **class under test**, which will help limit the using of the `PrivateAccessor` tool class for "unauthorized" operations unrelated to the current test. If you really need to access private members of other classes, you can remove the `@EnablePrivateAccess` annotation, or set the `verifyTargetOnCompile` parameter of the annotation to `false` to manually turn off the verification function.
|
||||
|
||||
@@ -1,5 +1,65 @@
|
||||
# Release Note
|
||||
|
||||
## 0.6.1
|
||||
- generate mock scanning log file automatically, for self-troubleshooting
|
||||
- fix a `Spock` test frame compatibility issue
|
||||
- fix a `Gradle` unit test runtime compatibility issue
|
||||
|
||||
## 0.6.0
|
||||
- add `OmniConstructor` and `OmniAccessor` tool for parameter preparation
|
||||
- fix a `ClassFormatError` caused by incorrect `FRAME FULL` operation
|
||||
- remove support for`diagnose` parameter of `@MockWith` annotation
|
||||
|
||||
## 0.5.2
|
||||
- support use`PrivateAccessor` to access private member in parent class
|
||||
- fix a class format error in jvm 1.8+ cause by incorrect bytecode generation
|
||||
- fix illegal field modifiers error when add mock class to an interface
|
||||
|
||||
## 0.5.1
|
||||
- use kotlin-style method descriptor in `VERBOSE` diagnose logs
|
||||
- support parameterized test of JUnit 5
|
||||
- fix an exception caused by method parameter with ternary operator
|
||||
- fix a bug cause log message lost when `@MockWith` annotation used
|
||||
|
||||
## 0.4.12
|
||||
- support verbose diagnose log for better self-troubleshooting
|
||||
- support disable private access target existence check
|
||||
- support specify mock scanning packages
|
||||
- fix an ArrayIndexOutOfBoundsException issue when transforming native method
|
||||
|
||||
## 0.4.11
|
||||
- support accessing private members of class under test in different package path
|
||||
- validate the number of private method parameters accessed by `PrivateAccessor`
|
||||
- fix a bug which may cause errors when the mock method contains array parameters
|
||||
- fix an issue which cause some private members not be found in the IntelliJ build
|
||||
|
||||
## 0.4.10
|
||||
- fix an issue of using mock in lambda expression
|
||||
- fix the NullPointerException when invoke private method with parameter value `null`
|
||||
|
||||
## 0.4.9
|
||||
- fix an issue cause by improperly bytecode processing while using `targetClass` parameter
|
||||
- auto validate access target of `PrivateAccessor`, improve resistance to code refactoring
|
||||
|
||||
## 0.4.8
|
||||
- fix an issue of private method invocation failed in assignment statements
|
||||
- support using mock for testing with `SpringRunner`
|
||||
- support `targetClass` parameter in `@MockMethod` annotation
|
||||
|
||||
## 0.4.7
|
||||
- fix incorrect stack size caused by `MOCK_CONTEXT` variable initialization
|
||||
- fix incorrect gradle build path of under Windows operating system
|
||||
|
||||
## 0.4.6
|
||||
- fix an issue of `IINC` bytecode processing
|
||||
- support `TestableTool.MOCK_CONTEXT` variable to inject extra parameters to mock context
|
||||
- use `TestableTool.TEST_CASE` variable to distinguish test case is no longer recommended
|
||||
|
||||
## 0.4.5
|
||||
- fix private access compile error in intelliJ 2020.3+ environment
|
||||
- change javaagent initialization logic to avoid NPE in cross-layer test scenario
|
||||
- support dump transformed byte code to local file for issue investigation
|
||||
|
||||
## 0.4.4
|
||||
- fix an issue of accessing private method with interface type parameter fail
|
||||
- fix an issue of mocking static method without parameter fail
|
||||
@@ -22,7 +82,7 @@
|
||||
- separate constant definition from `TestableTool` to `TestableConst`
|
||||
|
||||
## 0.3.2
|
||||
- support grable project for both private member access and quick mock
|
||||
- support gradle project for both private member access and quick mock
|
||||
- support access private static field and methods via PrivateAccessor
|
||||
|
||||
## 0.3.1
|
||||
@@ -39,12 +99,12 @@
|
||||
## v0.2.1
|
||||
- support mock static method
|
||||
- support mock kotlin companion object method
|
||||
- support mock invoke by interface / base class object
|
||||
- support mock invoke by an interface / base class object
|
||||
|
||||
## v0.2.0
|
||||
- use `TestableTool` class to expose test context and verify mock invoke
|
||||
- add `testable-maven-plugin` module to simplify javaagent configuration
|
||||
- remove dependence on EnableTestable annotation in `testable-agent`
|
||||
- remove dependence on `@EnableTestable` annotation in `testable-agent`
|
||||
- rename annotations to reflect the actual use
|
||||
|
||||
## v0.1.0
|
||||
|
||||
4
docs/en-us/doc/scope-of-mock.md
Normal file
4
docs/en-us/doc/scope-of-mock.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Effective Scope Of Mocking
|
||||
---
|
||||
|
||||
TO BE TRANSLATED
|
||||
@@ -1,11 +1,12 @@
|
||||
Use TestableMock
|
||||
---
|
||||
|
||||
`TestableMock` is an assist tool for Java unit testing based on source code and bytecode enhancement, including the following functions:
|
||||
`TestableMock` is now not only a lightweight and easy-to-use unit testing mock tool, but also a comprehensive set of auxiliary tools aimed at **simplifying Java unit testing**, including the following functions:
|
||||
|
||||
- [Quickly mock arbitrary call](en-us/doc/use-mock.md): quickly replace any method invocation in the class under test with a mock method, solve the cumbersome use of traditional mock tools problem
|
||||
- [Access private members of the class under test](en-us/doc/private-accessor.md): enable unit tests directly invoke or access private members of the class under test, solve the problems of private member initialization and private method testing
|
||||
- [Quick Mock arbitrary call](en-us/doc/use-mock.md): quickly replace any method invocation in the class under test with a mock method, solve the cumbersome use of traditional mock tools problem
|
||||
- [Auxiliary test void method](en-us/doc/test-void-method.md): use the Mock validator to check the internal logic of method, solve the problem that unit testing is difficult to implement to the method with no return value
|
||||
- [Quickly construct complicated parameter object](en-us/doc/omni-constructor.md):generate arbitrarily nested object instances, simplify their internal member assignment methods, solve the problem of long initialization codes for method parameters
|
||||
- [Auxiliary test void method](en-us/doc/test-void-method.md): use the mock validator to check the internal logic of method, solve the problem that unit testing is difficult to implement to the method with no return value
|
||||
|
||||
## Use in Maven project
|
||||
|
||||
@@ -15,7 +16,7 @@ It is recommended to add a `property` field that identifies the TestableMock ver
|
||||
|
||||
```xml
|
||||
<properties>
|
||||
<testable.version>0.4.4</testable.version>
|
||||
<testable.version>0.6.1</testable.version>
|
||||
</properties>
|
||||
```
|
||||
|
||||
@@ -62,12 +63,12 @@ Add dependence of `TestableMock` in `build.gradle` file:
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
testImplementation('com.alibaba.testable:testable-all:0.4.4')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.4.4')
|
||||
testImplementation('com.alibaba.testable:testable-all:0.6.1')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.6.1')
|
||||
}
|
||||
```
|
||||
|
||||
Then add javaagent to `test` configuration:
|
||||
Then add `javaagent` to "test" configuration:
|
||||
|
||||
```groovy
|
||||
test {
|
||||
@@ -76,3 +77,36 @@ test {
|
||||
```
|
||||
|
||||
See the [build.gradle](https://github.com/alibaba/testable-mock/blob/master/demo/java-demo/build.gradle) file of project `java-demo` and the [build.gradle.kts](https://github.com/alibaba/testable-mock/blob/master/demo/kotlin-demo/build.gradle.kts) file of project `kotlin-demo`.
|
||||
|
||||
> For Android project tested with `Robolectric` framework, please use the same method to add `TestableMock` dependency as above, and add `javaagent` configuration as follows:
|
||||
>
|
||||
> ```groovy
|
||||
> android {
|
||||
> testOptions {
|
||||
> unitTests {
|
||||
> all {
|
||||
> jvmArgs "-javaagent:${classpath.find { it.name.contains("testable-agent") }.absolutePath}"
|
||||
> }
|
||||
> }
|
||||
> }
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> See [issue-43](https://github.com/alibaba/testable-mock/issues/43) for a complete example.
|
||||
|
||||
> If the project is using `Spock` test framework, you need to specify the bytecode version generated by `Groovy` to be 1.6 or above, the method is as follows (please modify the properties value according to the actual JVM version used).
|
||||
>
|
||||
> For Maven project, add `<maven.compiler.source>` and `<maven.compiler.target>` properties inside the `pom.xml` file, e.g.
|
||||
> ```xml
|
||||
> <properties>
|
||||
> <maven.compiler.source>1.6</maven.compiler.source>
|
||||
> <maven.compiler.target>1.6</maven.compiler.target>
|
||||
> </properties>
|
||||
> ```
|
||||
>
|
||||
> For Gradle project, add a `sourceCompatibility` property inside the `build.gradle` file, e.g.
|
||||
> ```groovy
|
||||
> sourceCompatibility = '6'
|
||||
> ```
|
||||
>
|
||||
> See project `demo/spock-demo` for a complete example.
|
||||
|
||||
@@ -68,7 +68,7 @@ class Demo {
|
||||
}
|
||||
```
|
||||
|
||||
To test this method, you can use `TestableMock` to quickly mock out the `System.out.println` method. In the Mock method body, you can simply call the original method (equivalent to not affecting the original method function, only used for call recording), or leave it blank (equivalent to removing the side effects of the original method).
|
||||
To test this method, you can use `TestableMock` to quickly mock out the `System.out.println` method. In the mock method body, you can simply call the original method (equivalent to not affecting the original method function, only used for call recording), or leave it blank (equivalent to removing the side effects of the original method).
|
||||
|
||||
After executing the void type method under test, use `InvokeVerifier.verify()` to verify whether the incoming print content meets expectations:
|
||||
|
||||
@@ -76,11 +76,13 @@ After executing the void type method under test, use `InvokeVerifier.verify()` t
|
||||
class DemoTest {
|
||||
private Demo demo = new Demo();
|
||||
|
||||
// Intercept `System.out.println` invocation
|
||||
@MockMethod
|
||||
public void println(PrintStream ps, String msg) {
|
||||
// Execute the original call
|
||||
ps.println(msg);
|
||||
public static class Mock {
|
||||
// Intercept `System.out.println` invocation
|
||||
@MockMethod
|
||||
public void println(PrintStream ps, String msg) {
|
||||
// Execute the original call
|
||||
ps.println(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -3,38 +3,89 @@ Self-Help Troubleshooting
|
||||
|
||||
Compared with `Mockito` and other mock tools where developers have to manually inject mock classes, `TestableMock` uses method name and parameter type matching to automatically find invocations that require mock. While this mechanism brings convenience, it may also cause unexpected mock replacement.
|
||||
|
||||
To troubleshoot mock-related issues, just add the `@MockWith` annotation to the test class, and configure the parameter `diagnose` to `MockDiagnose.ENABLE`, so the detailed Mock method replacement process will be printed when the test is run.
|
||||
For this reason, `TestableMock` will automatically save the mock scanning log of the last test run in the project build directory. The default location is `target/testable-agent.log` (Maven project) or `build/testable-agent.log` (Gradle project).
|
||||
|
||||
```java
|
||||
@MockWith(diagnose = MockDiagnose.ENABLE)
|
||||
class DemoTest {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The output log example is as follows:
|
||||
Examples of log content are as follows:
|
||||
|
||||
```text
|
||||
[DIAGNOSE] Handling test class com/alibaba/testable/demo/DemoMockTest
|
||||
[DIAGNOSE] Handling source class com/alibaba/testable/demo/DemoMock
|
||||
[DIAGNOSE] Found 7 mock methods
|
||||
[DIAGNOSE] Handling method <init>
|
||||
[DIAGNOSE] Handling method newFunc
|
||||
[DIAGNOSE] Line 14, mock method createBlackBox used
|
||||
[DIAGNOSE] Handling method outerFunc
|
||||
[DIAGNOSE] Line 22, mock method innerFunc used
|
||||
[DIAGNOSE] Handling method commonFunc
|
||||
[DIAGNOSE] Line 29, mock method trim used
|
||||
[DIAGNOSE] Line 29, mock method sub used
|
||||
[DIAGNOSE] Line 29, mock method startsWith used
|
||||
[DIAGNOSE] Handling method getBox
|
||||
[DIAGNOSE] Line 36, mock method secretBox used
|
||||
[DIAGNOSE] Handling method callerOne
|
||||
[DIAGNOSE] Line 43, mock method callFromDifferentMethod used
|
||||
[DIAGNOSE] Handling method callerTwo
|
||||
[DIAGNOSE] Line 47, mock method callFromDifferentMethod used
|
||||
[DIAGNOSE] Handling method innerFunc
|
||||
[DIAGNOSE] Handling method callFromDifferentMethod
|
||||
[INFO] Start at Mon Jan 00 00:00:00 CST 0000
|
||||
... ...
|
||||
[INFO] Found test class com/alibaba/testable/demo/basic/DemoMockTest
|
||||
[INFO] Found 6 test cases
|
||||
[INFO] Found mock class com/alibaba/testable/demo/basic/DemoMockTest$Mock
|
||||
[INFO] Found 8 mock methods
|
||||
[INFO] Found source class com/alibaba/testable/demo/basic/DemoMock
|
||||
[INFO] Found method <init>
|
||||
[INFO] Found method newFunc
|
||||
[INFO] Line 19, mock method "createBlackBox" used
|
||||
[INFO] Found method outerFunc
|
||||
[INFO] Line 27, mock method "innerFunc" used
|
||||
[INFO] Line 27, mock method "staticFunc" used
|
||||
[INFO] Found method commonFunc
|
||||
[INFO] Line 34, mock method "trim" used
|
||||
[INFO] Line 34, mock method "sub" used
|
||||
[INFO] Line 34, mock method "startsWith" used
|
||||
... ...
|
||||
[INFO] Completed at Mon Jan 00 00:00:00 CST 0000
|
||||
```
|
||||
|
||||
The log shows all the mocked invocation and corresponding code line numbers in the class under test.
|
||||
|
||||
According to the targeted test classes, below are some simple clues for self-troubleshooting. Suppose the class under test is "com.demo.BizService", the test class is "com.demo.BizServiceTest", and the mock container class is "com.demo.BizServiceTest.Mock":
|
||||
|
||||
- If the log file is no generated, please check whether the `pom.xml` or `build.gradle` configuration correctly introduces `TestableMock` dependencies
|
||||
- If only `com/demo/BizServiceTest$Mock` is found in the output, please check whether the mock class is created at correct place
|
||||
- If both the test class and the mock class are found, but the class under test `com/demo/BizService` not appeared, please check whether the test class is in the same package of the class under test, and the name is "<ClassUnderTest>+Test", otherwise `@MockWith` annotation should be used
|
||||
- If all the three classes are found, and `Found method xxx` are output, but there is no mock replacement happen at the expected code line, please check whether the mock method definition matches the target method
|
||||
|
||||
For situations where expected mocking is not take effect, you could add a `@MockDiagnose` annotation to the mock class, and set the diagnosis level to `LogLevel.VERBOSE` for further investigation information.
|
||||
|
||||
```java
|
||||
class BizServiceTest {
|
||||
@MockDiagnose(LogLevel.VERBOSE)
|
||||
public static class Mock {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Executing the unit test again will print out the signatures of all mock methods, and the signatures of all invocations scanned in the class under test:
|
||||
|
||||
```text
|
||||
[INFO] Found test class com/alibaba/testable/demo/basic/DemoMockTest
|
||||
[TIP] Test case "should_mock_new_object"
|
||||
... ...
|
||||
[TIP] Test case "should_set_mock_context"
|
||||
[INFO] Found 6 test cases
|
||||
[INFO] Found mock class com/alibaba/testable/demo/basic/DemoMockTest$Mock
|
||||
[TIP] Mock constructor "createBlackBox" as "com.alibaba.demo.basic.model.mock.BlackBox(java.lang.String)"
|
||||
[TIP] Mock method "innerFunc" as "com.alibaba.demo.basic.DemoMock::innerFunc(java.lang.String) : java.lang.String"
|
||||
... ...
|
||||
[TIP] Mock method "callFromDifferentMethod" as "()Ljava/lang/String;"
|
||||
[INFO] Found 8 mock methods
|
||||
[INFO] Found source class com/alibaba/testable/demo/basic/DemoMock
|
||||
[INFO] Found method <init>
|
||||
[TIP] Line 13, constructing "java.lang.Object()"
|
||||
[INFO] Found method newFunc
|
||||
[TIP] Line 19, constructing "com.alibaba.demo.basic.model.mock.BlackBox(java.lang.String)"
|
||||
[INFO] Line 19, mock method "createBlackBox" used
|
||||
[TIP] Line 19, invoking "com.alibaba.demo.basic.DemoMockTest$Mock::createBlackBox(java.lang.String) : com.alibaba.demo.basic.model.mock.BlackBox"
|
||||
[TIP] Line 20, invoking "com.alibaba.demo.basic.model.mock.BlackBox::get() : java.lang.String"
|
||||
[INFO] Found method outerFunc
|
||||
[TIP] Line 27, constructing "java.lang.StringBuilder()"
|
||||
[TIP] Line 27, invoking "java.lang.StringBuilder::append(java.lang.String) : java.lang.StringBuilder"
|
||||
[TIP] Line 27, invoking "com.alibaba.demo.basic.DemoMock::innerFunc(java.lang.String) : java.lang.String"
|
||||
[INFO] Line 27, mock method "innerFunc" used
|
||||
... ...
|
||||
```
|
||||
|
||||
The logs are formatted in follow pattern:
|
||||
|
||||
- `Mock constructor "<MockMethodName>" as "<Signature>" for "<TypeName>"` Mock constructor found in test class
|
||||
- `Mock method "<MockMethodName>" as "<Signature>"` Mock method found in test class (the first parameter that identify the mock target class is currently kept)
|
||||
- `Line XX, constructing "<TypeName>" as "<Signature>"` Constructor invocation found in test under class
|
||||
- `Line XX, invoking "<MethodName>" as "<Signature>"` Member method invocation found in test under class
|
||||
|
||||
> In order to clearly distinguish between the type of method return value and the type of invoker, the method signature recorded in the log uses a method definition structure similar to `Kotlin`.
|
||||
|
||||
Comparing the actual signature of the original call with the signature defined by the mock method, the problem is usually found quickly.
|
||||
|
||||
67
docs/en-us/doc/upgrade-guide.md
Normal file
67
docs/en-us/doc/upgrade-guide.md
Normal file
@@ -0,0 +1,67 @@
|
||||
Upgrade Guide
|
||||
---
|
||||
|
||||
### Upgrade to v0.6
|
||||
|
||||
In version `0.6`,`TestableMock` provided a new [quick complicated class construction](en-us/doc/omni-constructor.md) capability. Meanwhile, it includes a modification that is incompatible with the `0.5` version: class `PrivateAccessor` have been moved from `com.alibaba.testable.core.accessor` package to `com.alibaba.testable.core.tool` package.
|
||||
|
||||
If you are using `PrivateAccessor` and having import statement like this:
|
||||
|
||||
```java
|
||||
import com.alibaba.testable.core.accessor.PrivateAccessor;
|
||||
```
|
||||
|
||||
When upgrade `TestableMock` to `0.6` version, please replace it as:
|
||||
|
||||
```java
|
||||
import com.alibaba.testable.core.tool.PrivateAccessor;
|
||||
```
|
||||
|
||||
### Upgrade to v0.5
|
||||
|
||||
The `0.5` version solves the three historical problems left over before:
|
||||
|
||||
1. <s>**Mock method cannot call other non-static methods**</s>. The mock method in the new version no longer has any difference from the ordinary method, and can access any external method and member variable.
|
||||
2. <s>**Mock method always acts on the entire test life cycle**</s>. From now on, the mock method supports restricting the effective scope to the test cases in the test class of which it belongs, so there is no need to worry about accidentally mocking cross-class test invocations.
|
||||
3. <s>**The MOCK_CONTEXT needs manually cleaned up and only supports class-level parallel testing**</s>. Now each test case has an independent `MOCK_CONTEXT` variable, no need to clean up after used, and you can use unit test with any parallel level.
|
||||
|
||||
In order to better realize the reuse of Mock methods, the version `0.5` have made a clear boundary between the mock class and the test class in the new version. When upgrading from `0.4` to `0.5`, the only change required is to wrap all mock methods in the test class with a `public static class Mock {}`.
|
||||
|
||||
For example, the original test class definition was as follows:
|
||||
|
||||
```java
|
||||
public class DemoMockTest {
|
||||
@MockMethod(targetClass = DemoMock.class)
|
||||
private String innerFunc(String text) {
|
||||
return "hello_" + text;
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_mock_member_method() throws Exception {
|
||||
assertEquals("hello_world", demoMock.outerFunc());
|
||||
verify("innerFunc").with("world");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After upgrading to the `0.5` version, move all mock methods (in this example, only the `innerFunc` method) to a static inner class named `Mock`, which is equivalent to adding two lines of code:
|
||||
|
||||
```java
|
||||
public class DemoMockTest {
|
||||
|
||||
public static class Mock { // Add this line
|
||||
@MockMethod(targetClass = DemoMock.class)
|
||||
private String innerFunc(String text) {
|
||||
return "hello_" + text;
|
||||
}
|
||||
} // Add this line
|
||||
|
||||
@Test
|
||||
void should_mock_member_method() throws Exception {
|
||||
assertEquals("hello_world", demoMock.outerFunc());
|
||||
verify("innerFunc").with("world");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Finally, upgrade the `TestableMock` dependency in `pom.xml` or `build.gradle` file to the new version.
|
||||
18
docs/en-us/doc/use-in-ide.md
Normal file
18
docs/en-us/doc/use-in-ide.md
Normal file
@@ -0,0 +1,18 @@
|
||||
Use TestableMock In IDE
|
||||
---
|
||||
|
||||
## Use IntelliJ IDE
|
||||
|
||||
IntelliJ IDE supports the `JSR-269` annotation processor and the `maven-surefire-plugin` arguments very well (both are techniques back the `TestableMock`). Usually you don't need any special configuration to make everything work, it's all out of the box.
|
||||
|
||||
## Use Eclipse IDE
|
||||
|
||||
Since the built-in compilation feature of `Eclipse` is based on a self-made compiler, it is not compatible with the standard `javac` compilation process, which will cause the `@EnablePrivateAccess` annotation to be invalid when running test cases in the IDE. However, the function of accessing the private members of the class under test through the `PrivateAccessor` tool class will not be affected by differences in the compiler.
|
||||
|
||||
If the `@EnablePrivateAccess` annotation is used in the project, you can use `mvn test -Dtest=<TestClassName>` and `mvn test -Dtest=<TestClassName>#<TestCaseName>` in the command line of `Eclipse` to run a single test class or test case.
|
||||
|
||||
At the same time, because the built-in unit test executor of `Eclipse` completely ignores the configuration of the `pom.xml` file, additional configuration is required to use the Mock function.
|
||||
|
||||
Take the use of `JUnit` as an example. You need to pull down from the small triangle next to the run button on the IDE toolbar, select "Run Configurations...", select the task to run the unit test on the left side, and switch to "arguments" Tab on the right side, append a `-javaagent:` parameter in the "VM Options", the following figure is an example, note that the `testable-agent` package should be modified to match the actual situation of the local Maven repository path.
|
||||
|
||||

|
||||
4
docs/en-us/doc/use-mock-with.md
Normal file
4
docs/en-us/doc/use-mock-with.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Use MockWith Annotation
|
||||
---
|
||||
|
||||
TO BE TRANSLATED
|
||||
179
docs/en-us/doc/use-mock.md
Normal file
179
docs/en-us/doc/use-mock.md
Normal file
@@ -0,0 +1,179 @@
|
||||
Fast Mocking
|
||||
---
|
||||
|
||||
In unit testing, the main role of the mock method is to replace those methods with **need external dependencies**, **with time-consuming**, **has random results**, or other effects that affect the development of the test, but do not affect the key the logic to be tested. Generally speaking, a certain call needs to be mocked, which is usually only related to its own characteristics, and has nothing to do with the source of the invocation.
|
||||
|
||||
Based on the above information, `TestableMock` has designed a minimalist mock mechanism. Unlike the common mock tools that uses **class** as the definition granularity of mocking, and repeats the description of mock behavior in each test case, `TestableMock` allows each business class (class under test) to be associated with a set of reusable collection of mock methods (carried by the mock container class), following the principle of "convention over configuration", and mock method replacement will automatically happen when the specified method in the test class match an invocation in the class under test.
|
||||
|
||||
> In summary, there are two simple rules:
|
||||
> - Mock non-constructive method, copy the original method definition to the mock class, add a `@MockMethod` annotation
|
||||
> - Mock construction method, copy the original method definition to the mock class, replace the return value with the constructed type, the method name is arbitrary, and add a `@MockContructor` annotation
|
||||
|
||||
The detail mock method definition convention is as follows.
|
||||
|
||||
### 0. Pre-step, prepare the mock class
|
||||
|
||||
First, create a mock class as the container for mock methods associated with the test class. The simplest way is to add a static inner class named `Mock` to the test class. E.g:
|
||||
|
||||
```java
|
||||
public class DemoTest {
|
||||
|
||||
public static class Mock {
|
||||
// mock methods goes here
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### 1.1 Mock method calls of any class
|
||||
|
||||
Define an ordinary method annotated with `@MockMethod` in the mock class with exactly the same signature (name, parameter, and return value type) as the method to be mocked, and then add the type of target object (which the method originally belongs to) as `targetMethod` parameter of `@MockMethod` annotation.
|
||||
|
||||
At this time, all invocations to that original method in the class under test will be automatically replaced with invocations to the above-mentioned mock method when the unit test is running.
|
||||
|
||||
For example, there is a call to `"anything".substring(1, 2)` in the class under test, and we want to change it to a fixed string when running the test, we only need to define the following method in the mock class:
|
||||
|
||||
```java
|
||||
// The original method signature is `String substring(int, int)`
|
||||
// The object `"anything"` that invokes this method is of type `String`
|
||||
@MockMethod(targetClass = String.class)
|
||||
private String substring(int i, int j) {
|
||||
return "sub_string";
|
||||
}
|
||||
```
|
||||
|
||||
When several methods to be mocked have the same name, you can put the name of the method to be mocked in the `targetMethod` parameter of `@MockMethod` annotation, so that the mock method itself can be named at will.
|
||||
|
||||
The following example shows the usage of the `targetMethod` parameter, and its effect is the same as the above example:
|
||||
|
||||
```java
|
||||
// Use `targetMethod` to specify the name of the method that needs to be mocked
|
||||
// The method itself can now be named arbitrarily, but the method parameters still need to follow the same matching rules
|
||||
@MockMethod(targetClass = String.class, targetMethod = "substring")
|
||||
private String use_any_mock_method_name(int i, int j) {
|
||||
return "sub_string";
|
||||
}
|
||||
```
|
||||
|
||||
Sometimes, the mock method need to access the member variables in the original object that initiated the invocation, or invoke other methods of the original object. At this point, you can remove the `targetClass` parameter in the `@MockMethod` annotation, and then add a extra parameter whose type is the original object type of the method to the first index of the method parameter list.
|
||||
|
||||
The `TestableMock` convention is that when the `targetClass` parameter of the `@MockMethod` annotation is not defined, the first parameter of the mock method is the type of the target method, and the parameter name is arbitrary. In order to facilitate code reading, it is recommended to name this parameter as `self` or `src`. Example as follows:
|
||||
|
||||
```java
|
||||
// Adds a `String` type parameter to the first position the mock method parameter list (parameter name is arbitrary)
|
||||
// This parameter can be used to get the value and context of the actual invoker at runtime
|
||||
@MockMethod
|
||||
private String substring(String self, int i, int j) {
|
||||
// Call the original method is also allowed
|
||||
return self.substring(i, j);
|
||||
}
|
||||
```
|
||||
|
||||
For complete code examples, see the `should_mock_common_method()` test cases in the `java-demo` and `kotlin-demo` sample projects. (Because `Kotlin` has made magical changes to the String type, the method under test in the `Kotlin` example adds a layer of encapsulation to the `BlackBox` class)
|
||||
|
||||
### 1.2 Mock the member method of the class under test itself
|
||||
|
||||
Sometimes, when testing certain methods, it is desirable to mock out some other member methods of the class under test itself.
|
||||
|
||||
The solution is the same as the previous case. Just set `targetClass` parameter value to the type of class under test.
|
||||
|
||||
For example, there is a private method with the signature `String innerFunc(String)` in the class under test. If we want to replace it during testing, we only need to define the following method in the mock class:
|
||||
|
||||
```java
|
||||
// The type to test is `DemoMock`
|
||||
@MockMethod(targetClass = DemoMock.class)
|
||||
private String innerFunc(String text) {
|
||||
return "mock_" + text;
|
||||
}
|
||||
```
|
||||
|
||||
Similarly, if the method in the above example needs to access the original tested object that initiated the call, it may not use the `targetClass` parameter, but when defining the mock method, add a parameter of type `DemoMock` to the first index of the method parameter list.
|
||||
|
||||
For complete code examples, see the `should_mock_member_method()` test case in the `java-demo` and `kotlin-demo` sample projects.
|
||||
|
||||
### 1.3 Mock static methods of any class
|
||||
|
||||
Mock for static methods is the same as for any ordinary methods.
|
||||
|
||||
For example, if the static method `secretBox()` of the `BlackBox` type is invoked in the class under test, and the method signature is `BlackBox secretBox()`, then the mock method is as follows:
|
||||
|
||||
```java
|
||||
@MockMethod(targetClass = BlackBox.class)
|
||||
private BlackBox secretBox() {
|
||||
return new BlackBox("not_secret_box");
|
||||
}
|
||||
```
|
||||
|
||||
For complete code examples, see the `should_mock_static_method()` test case in the `java-demo` and `kotlin-demo` sample projects.
|
||||
|
||||
### 1.4 Mock `new` operation of any type
|
||||
|
||||
Define an ordinary method annotated with `@MockContructor` in the mock class, make the return value type of the method the type of the object to be created, and the method parameters are exactly the same as the constructor parameters to be mocked, the method name is arbitrary.
|
||||
|
||||
At this time, all operations in the class under test that use `new` to create the specified class (and use the constructor that is consistent with the mock method parameters) will be replaced with calls to the custom method.
|
||||
|
||||
For example, if there is a call to `new BlackBox("something")` in the class under test, and you want to change it during unit testing (usually by replacing it with a mock object, or by replacing it with a temporary object created with test parameters), just define the following mock method:
|
||||
|
||||
```java
|
||||
// The signature of the constructor to be mocked is `BlackBox(String)`
|
||||
// No need to add additional parameters to the mock method parameter list, and the name of the mock method is arbitrary
|
||||
@MockContructor
|
||||
private BlackBox createBlackBox(String text) {
|
||||
return new BlackBox("mock_" + text);
|
||||
}
|
||||
```
|
||||
|
||||
> You can still use the `@MockMethod` annotation, and configure the `targetMethod` parameter value to `"<init>"`, and the rest is the same as above. The effect is the same as using the `@MockContructor` annotation
|
||||
|
||||
For complete code examples, see the `should_mock_new_object()` test case in the `java-demo` and `kotlin-demo` sample projects.
|
||||
|
||||
### 2. Identify different invocation source in mock method
|
||||
|
||||
In the mock method, you can use the `TestableTool.SOURCE_METHOD` variable to identify **the method name of the class under test before entering the mock method**; in addition, the `TestableTool.MOCK_CONTEXT` variable can **inject additional context parameters into the mock method**, to distinguish and process different calling scenarios.
|
||||
|
||||
For example, to verify the impact on the target method under test when the mock method returns different results in the test case:
|
||||
|
||||
```java
|
||||
@Test
|
||||
public void testDemo() {
|
||||
MOCK_CONTEXT.set("case", "data-ready");
|
||||
assertEquals(true, demo());
|
||||
MOCK_CONTEXT.set("case", "has-error");
|
||||
assertEquals(false, demo());
|
||||
}
|
||||
```
|
||||
|
||||
Take out the injected parameters in the mock method and return different results according to the situation:
|
||||
|
||||
```java
|
||||
@MockMethod
|
||||
private Data mockDemo() {
|
||||
switch((String)MOCK_CONTEXT.get("case")) {
|
||||
case "data-ready":
|
||||
return new Data();
|
||||
case "has-error":
|
||||
throw new NetworkException();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For complete code examples, see the `should_get_source_method_name()` and `should_get_test_case_name()` test cases in the `java-demo` and `kotlin-demo` sample projects.
|
||||
|
||||
### 3. Verify the sequence and parameters of the mock method being invoked
|
||||
|
||||
In test cases, you can use the `InvokeVerifier.verify()` method, and cooperate with `with()`, `withInOrder()`, `without()`, `withTimes()` and other methods to verify the mock call situation.
|
||||
|
||||
For details, please refer to the [Check Mock Call](en-us/doc/matcher.md) document.
|
||||
|
||||
### 4. Special instructions
|
||||
|
||||
> **Naming Conventions for Test Classes and Mock Containers**
|
||||
>
|
||||
> By default, `TestableMock` assumes that the <u>package path of the test class and the class under test are the same, and the name is `<ClassUnderTest>+Test`</u> (usually Java project with `Maven` or `Gradle` conform to this convention).
|
||||
> At the same time, it is agreed that the mock container associated with the test class is <u>in its internal static class named `Mock`</u>, or <u>an independent class named `<ClassUnderTest>+Mock` under the same package path</u>.
|
||||
>
|
||||
> When the test class or mock container path does not follow to this convention, you can use the `@MockWith` annotation to specify it explicitly. For details, see [Use MockWith Annotation](en-us/doc/use-mock-with.md).
|
||||
|
||||
For more implementation details of `TestableMock`, please refer to the [Design and Principle](en-us/doc/design-and-mechanism.md) document.
|
||||
@@ -1,16 +1,26 @@
|
||||
- Quick Start
|
||||
- [Use TestableMock](en-us/doc/setup.md)
|
||||
- [Private Accessor](en-us/doc/private-accessor.md)
|
||||
- [Fast Mocking](en-us/doc/use-mock.md)
|
||||
- [Private Accessor](en-us/doc/private-accessor.md)
|
||||
- [Omni Constructor](en-us/doc/omni-constructor.md)
|
||||
- [Test Void Method](en-us/doc/test-void-method.md)
|
||||
|
||||
- Usage Reference
|
||||
- Usage Guide
|
||||
- [Verify Mock Invocation](en-us/doc/invoke-matcher.md)
|
||||
- [Effective Scope Of Mocking](zh-cn/doc/scope-of-mock.md)
|
||||
- [Reusing Mock Methods](zh-cn/doc/mock-method-reusing.md)
|
||||
- [Use MockWith Annotation](zh-cn/doc/use-mock-with.md)
|
||||
- [Frequently Asked Questions](en-us/doc/frequently-asked-questions.md)
|
||||
- [Use TestableMock In IDE](en-us/doc/use-in-ide.md)
|
||||
- [Self-Help Troubleshooting](en-us/doc/troubleshooting.md)
|
||||
- [Testable Maven Plugin](en-us/doc/use-maven-plugin.md)
|
||||
|
||||
- Technical Reference
|
||||
- [Upgrade Guide](en-us/doc/upgrade-guide.md)
|
||||
- [Available Global Arguments](zh-cn/doc/javaagent-args.md)
|
||||
- [Available Annotations](zh-cn/doc/annotations.md)
|
||||
- [How Mock Works](zh-cn/doc/design-and-mechanism.md)
|
||||
- [Mock Tools Comparison](en-us/doc/comparation.md)
|
||||
- [Release Note](en-us/doc/release-note.md)
|
||||
- [About Us](en-us/doc/about-us.md)
|
||||
- [Feedback Channel](en-us/doc/feedback.md)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user