Compare commits

...

111 Commits

Author SHA1 Message Date
金戟
30570ae69d fix: verifier should compare arrays in deep 2023-05-17 19:08:42 +08:00
金戟
320ee632b7 test: add cases of collection tool methods 2022-12-17 08:12:54 +08:00
金戟
ba284f6518 doc: introduce CollectionTool class 2022-12-16 23:09:19 +08:00
金戟
c6e861e5cf refactor: move fastListOf method out of CollectionTool 2022-12-15 09:42:02 +08:00
金戟
0287d27d85 release v0.7.9 2022-12-14 19:25:55 +08:00
金戟
1e9079e8d3 feat: move collection creation methods to CollectionTool 2022-12-14 00:34:03 +08:00
金戟
e7a2b06698 feat: generate none-null object for classes not in jvm package 2022-12-13 23:30:47 +08:00
金戟
ab87ef1fd0 feat: cache constructor parameter types for efficiency 2022-12-12 08:20:58 +08:00
金戟
d5c1f780de feat: invoke super constructor explicitly in generated class 2022-12-11 20:06:46 +08:00
金戟
a463bcbfd9 fix: correctly handle array type 2022-12-11 12:03:37 +08:00
金戟
c53f1e01f0 feat: find best constructor of super class 2022-12-11 08:10:46 +08:00
金戟
12989aa612 refactor: merge UNREACHABLE_CLASSES and PRELOADED_CLASSES 2022-12-10 12:22:37 +08:00
金戟
61a3090431 refactor: update junit version 2022-12-09 22:09:12 +08:00
金戟
e9ce61d83c doc: fix test case name 2022-12-06 11:59:57 +08:00
金戟
d9f65189f7 feat: add RICH_INTERFACE option to avoid introduce omni class in system interface implementation 2022-12-04 17:52:19 +08:00
金戟
1446382f53 feat: handle many special cases of system class creation 2022-12-04 07:34:42 +08:00
金戟
ada70f88c0 feat: get generic type mapping recursively 2022-12-03 17:15:18 +08:00
金戟
65b58d0861 feat: avoid duplicate method declaration 2022-12-02 23:17:13 +08:00
金戟
a67dd7b73f feat: add targetClassName parameter to mock invoke annotation 2022-12-01 23:41:41 +08:00
金戟
1f6fc2e4bc feat: support generate system interface class 2022-12-01 00:32:21 +08:00
金戟
eb8ccb0a40 feat: choose correct constructor for system preloaded classes 2022-11-30 00:02:42 +08:00
金戟
dcbb305e10 fix: incorrect onmi constructor option judgement 2022-11-27 21:13:05 +08:00
金戟
8c0a74264a refactor: move string util methods to core package 2022-11-27 21:10:53 +08:00
金戟
dcc5108149 fix: look for outer mock class failed 2022-11-26 22:05:24 +08:00
金戟
0a40e07739 feat: upgrade transmittable-thread-local to 2.14.2 2022-11-26 22:04:53 +08:00
金戟
9e5f722076 fix: duplicate naming 2022-11-25 23:05:49 +08:00
金戟
1dcbe6b0f2 feat: get rid of Pair class 2022-11-25 15:00:04 +08:00
金戟
e4ff8ce57e fix: typo and condition in comment 2022-11-24 22:13:06 +08:00
金戟
47871d0eb6 feat: always consider class with MockContainer annotation as mock class 2022-11-23 14:05:10 +08:00
金戟
e52538acf3 feat: parse mock class considering MockContainer annotation 2022-11-21 00:41:27 +08:00
金戟
db91a0ea34 feat: transform mock class according to MockContainer annotation 2022-11-20 20:11:46 +08:00
金戟
d912e53ebc feat: implement EXCEPT_INTERFACE option of omni constructor 2022-11-20 13:09:04 +08:00
金戟
1e2e1e6d84 doc: update test case name 2022-11-20 00:05:52 +08:00
金戟
1bb6007eb5 test: also verify mock invocation times 2022-11-20 00:01:24 +08:00
金戟
6632fae529 feat: add testcase for inherited mock container 2022-11-19 23:32:06 +08:00
金戟
9018301bfc feat: add MockContainer annotation 2022-11-19 22:28:28 +08:00
金戟
dbaeb3bcf1 doc: should do both push and push tag 2022-11-17 21:05:15 +08:00
金戟
fc7e4af2c6 feat: support generate class with inherited genericity 2022-11-14 20:36:44 +08:00
金戟
76cff45123 feat: support generate class with dollar in name 2022-11-14 20:36:37 +08:00
金戟
5c8ef00fce feat: support generate parameterized class 2022-11-14 20:36:29 +08:00
金戟
f8501b0722 fix: generate correct type name 2022-11-14 22:40:34 +08:00
金戟
46e4499035 release v0.7.8 2022-11-13 20:13:40 +08:00
金戟
f508f7aba9 refactor: always use the collection method in core module 2022-11-13 18:15:42 +08:00
金戟
6e029f61d8 feat: add util method for quick create set 2022-11-01 16:35:47 +08:00
金戟
b654a3cfaa feat: support generate method with template types 2022-10-09 20:31:13 +08:00
金戟
038ae8dba0 feat: support construct simple interface and abstract class 2022-10-08 20:20:18 +08:00
金戟
11a5565cee feat: use same package path of parent to avoid access error 2022-10-08 11:30:43 +08:00
金戟
ba77a0138c fix: let the kotlin testcases pass with 'mvn test' locally 2022-09-29 17:22:35 +08:00
金戟
9788dd9ecc feat: add common used collection creation function to library 2022-08-09 14:25:52 +08:00
金戟
55a6e141fc doc: update faq to fit current mock inovke annotation parameter 2022-06-11 22:48:26 +08:00
金戟
81a2817b6c fix: fit for jvm 1.6 2022-06-04 23:23:59 +08:00
金戟
ef6099a63d feat: generate empty sub class for interface 2022-06-04 23:11:12 +08:00
金戟
3c11676408 feat: add original InMemoryJavaCompiler 2022-06-03 13:29:25 +08:00
金戟
b2a87bffbe refactor: use constant dollar symbol 2022-05-29 14:08:35 +08:00
金戟
1a2083416e doc: describe the MockContext fields 2022-05-29 11:35:23 +08:00
金戟
c1e6d00953 doc: one more thing to mention 2022-05-28 17:39:35 +08:00
金戟
39b742b290 release v0.7.7 2022-05-28 13:34:27 +08:00
金戟
0a5666c0be fix: only generate named lambda method for mocked ones 2022-05-28 13:26:37 +08:00
金戟
006e742c28 fix: upgrade asm to support java 17 2022-05-19 19:00:59 +08:00
金戟
237b9ebb15 doc: fix link to invoke matcher page 2022-05-19 18:53:13 +08:00
金戟
7aedcf63b5 release v0.7.6 2022-04-22 17:14:33 +08:00
金戟
0f64d52718 feat: should skip mockito generated class 2022-04-22 17:09:56 +08:00
金戟
4ed1323a62 feat: package mapping map shuold be ordered 2022-04-22 16:38:26 +08:00
Fan Lin
ae1ba8b4ff Merge pull request #274 from zcbbpo/ISSUES-272
Fixed java9+ String makeConcatWithConstants not work for method refer…
2022-04-01 17:01:30 +08:00
zcbbpo
00f25d3622 Fixed java9+ String makeConcatWithConstants not work for method reference 2022-04-01 11:14:49 +08:00
金戟
2e58ae8360 release v0.7.5 2022-03-08 12:38:35 +08:00
金戟
ff54c57608 fix: should handle array type method properly 2022-03-08 12:35:18 +08:00
金戟
351b3399e6 feat: elastic plugin class should contain only one constructor 2022-02-20 22:22:00 +08:00
金戟
df9e2395cc doc: describe no unique public constructor error 2022-02-20 22:09:45 +08:00
金戟
b9064d3d47 doc: intellij plugin usage english edition 2022-02-05 22:52:48 +08:00
金戟
c967456569 doc: add intellij plugin usage 2022-02-05 21:28:28 +08:00
金戟
692b4435aa release v0.7.4 2022-02-02 21:02:28 +08:00
金戟
6379f16266 fix #261, java.net.URL is loaded before OmniClassHandler 2022-02-02 21:02:28 +08:00
Fan Lin
63a6a9fdca Merge pull request #259 from oldratlee/upgrade-ttl
upgrade TTL to 2.12.4, fix ClassCircularityError
2022-01-12 10:05:23 +08:00
Jerry Lee
7e24503611 upgrade TTL to 2.12.4, fix ClassCircularityError
more discussion see
https://github.com/alibaba/transmittable-thread-local/issues/278
2022-01-12 00:43:02 +08:00
金戟
3120eba3af update release note 2022-01-10 20:55:39 +08:00
金戟
02f15177fc release v0.7.3 2022-01-09 22:45:14 +08:00
Fan Lin
a25f14c0ec Merge pull request #257 from zcbbpo/FIXED-INVOKEINTERFACE_1
Fixed invokeinterface issuse
2022-01-07 14:55:25 +08:00
jimcao
635cbf62fb completed 2022-01-06 15:13:32 +08:00
jimcao
17b31b39dd refine code 2022-01-06 13:13:09 +08:00
jimcao
0d43d93a29 temp update 2022-01-06 11:52:18 +08:00
jimcao
66db3cff7d temp 2022-01-05 09:49:49 +08:00
jimcao
3343a26de8 Add interface static test case 2022-01-05 09:12:38 +08:00
jimcao
de2e520d95 Fixed interface default method 2022-01-04 18:02:49 +08:00
金戟
59bcbfdf37 release v0.7.2 2021-12-17 14:39:20 +08:00
Fan Lin
cdad0d2446 Merge pull request #251 from zcbbpo/Fxied-static-instance-reference-issue
fixed static instance reference issue
2021-12-17 13:40:45 +08:00
jimcao
ba5102ffd9 fixed static instance reference issue 2021-12-16 17:26:38 +08:00
金戟
db4e02a201 release v0.7.1 2021-12-11 22:59:49 +08:00
金戟
fda7ee23c7 fix #248, should not look up package mapping if found mock class 2021-12-11 14:40:27 +08:00
金戟
140e54983b release v0.7.0 2021-11-07 11:07:39 +08:00
金戟
548d7ba21f add upgrade guide for 0.7.0 2021-11-07 11:06:37 +08:00
金戟
c2d456da97 use MockInvoke and MockNew 2021-11-03 11:44:44 +08:00
金戟
28500558fc rename verify() to verifyInvoked(), avoid name conflict with system method 2021-11-02 23:39:48 +08:00
金戟
990f407ebe should throw NullPointerException when invoke mock method on null object (issue-163) 2021-11-02 23:09:57 +08:00
金戟
f6206ee294 add release note of 0.6.10 2021-10-31 23:36:32 +08:00
金戟
8cfba333ff release v0.6.10 2021-10-31 14:24:10 +08:00
Fan Lin
397fc3d2fa Merge pull request #234 from zcbbpo/FIXED-LAMBDA-MR
[Lambda] Fixed method reference external var
2021-10-31 14:19:02 +08:00
jim cao
b7a076dfaf fixed external var 2021-10-30 19:40:21 +08:00
金戟
aee2ec2767 fix typo and format code 2021-10-27 07:33:08 +08:00
Fan Lin
682d822249 Merge pull request #231 from HankDevelop/0.6
接口继承支持
2021-10-27 07:27:46 +08:00
Fan Lin
6cf3f993ca Merge branch 'master' into 0.6 2021-10-27 07:27:23 +08:00
HankDevelop
0cce5a6ec0 接口继承支持 2021-10-26 19:57:10 +08:00
金戟
0917406c44 release v0.6.9 2021-10-17 22:56:33 +08:00
金戟
3bf19ee6d0 fix NullPointerException when passing null as parameter of private accessor 2021-10-17 22:56:33 +08:00
Fan Lin
e1afb1cd54 Merge pull request #223 from Augustine-C/fix_flaky
Fixed a flaky test in core.tool.OmniAccessorTest
2021-09-30 10:47:59 +08:00
Augustine Cui
10953ff02a Fixed a flaky test in core.tool.OmniAccessorTest
This test is flaky since the method `java.lang.Class.getDeclaredFields` is non-deterministic, and `GetDeclaredFilelds`  is ultimately used in the `com.alibaba.testable.core.util.TypeUtil.getAllFields`
I converted the result to a `HashSet` to avoid ordering issues, so the specific ordering of the fields would not affect the result. I think this change can prevent test failure due to future change in the JVM.
2021-09-29 17:10:37 -07:00
金戟
80b0f3b878 do not introduce sun package dependence 2021-09-28 21:26:16 +08:00
Fan Lin
8fbd459280 Merge pull request #208 from zcbbpo/FIXED-LAMBDA-METHOD-REFERENCE-ISSUES
Fixed method reference
2021-09-28 20:46:51 +08:00
jimcao
f3309f933e fixed method reference 2021-09-24 15:20:13 +08:00
金戟
b47204e329 hold images in cdn 2021-09-15 21:11:29 +08:00
金戟
6616ff1174 add description about modify static final member 2021-08-09 08:56:40 +08:00
159 changed files with 5012 additions and 918 deletions

View File

@@ -2,14 +2,14 @@
换种思路写Mock让单元测试更简单。
无需初始化不挑服务框架甭管要换的是私有方法、静态方法、构造方法还是其他任何类的任何方法也甭管要换的对象是怎么创建的。写好Mock定义加个`@MockMethod`注解,一切统统搞定。
无需初始化不挑服务框架甭管要换的是私有方法、静态方法、构造方法还是其他任何类的任何方法也甭管要换的对象是怎么创建的。写好Mock定义加个`@MockInvoke`注解,一切统统搞定。
- 文档https://alibaba.github.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)
<font size="5">**0.7版本已发布**</font>,从`0.6.x`升级到`0.7.x`版本请参考[版本升级指南](https://alibaba.github.io/testable-mock/#/zh-cn/doc/upgrade-guide)
-----
@@ -31,7 +31,9 @@
|-- tool ➜ 项目开发过程中的工具脚本
|-- demo
| |-- java-demo ➜ Java语言的示例代码
| `-- kotlin-demo ➜ Kotlin语言的示例代码
| |-- kotlin-demo ➜ Kotlin语言的示例代码
| |-- android-demo ➜ Android项目的示例代码
| `-- spock-demo ➜ Spock测试框架的示例代码
`-- docs ➜ 项目使用文档
```

View File

@@ -3,17 +3,13 @@
Write mock faster, make unit testing easier.
Any test framework, no initialization, no matter private method, static method, construction method, or any other method of any class, and no matter how the object created.
Write a mock method, add an `@MockMethod` annotation, everything is done.
Write a mock method, add an `@MockInvoke` 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-refactoring support of mocking, allow quick mock all method in specified class
- `v1.0` all functions are stable, a brand-new start
`TestableMock` is still under heavy development, if you get sucked somehow, just go ahead raise an [issue](https://github.com/alibaba/testable-mock/issues) for it.
## Directory Structure
@@ -27,7 +23,9 @@ Usage Document: https://alibaba.github.io/testable-mock/#/en-us/
|-- tool ➜ Scripts for project maintain
|-- demo
| |-- java-demo ➜ Java code example
| `-- kotlin-demo ➜ Kotlin code example
| |-- kotlin-demo ➜ Kotlin code example
| |-- android-demo ➜ Android app code example
| `-- spock-demo ➜ Spock framwork code example
`-- docs ➜ Source code of usage document
```

View File

@@ -49,7 +49,7 @@ dependencies {
testImplementation 'androidx.test:runner:1.4.0-alpha05'
testImplementation 'junit:junit:4.+'
testImplementation 'org.robolectric:robolectric:4.5.1'
testImplementation 'com.alibaba.testable:testable-all:0.6.8'
testImplementation 'com.alibaba.testable:testable-all:0.7.9'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

View File

@@ -1,15 +1,15 @@
package com.alibaba.testable.demo;
import com.alibaba.testable.demo.model.BlackBox;
import com.alibaba.testable.core.annotation.MockConstructor;
import com.alibaba.testable.core.annotation.MockMethod;
import com.alibaba.testable.core.annotation.MockNew;
import com.alibaba.testable.core.annotation.MockInvoke;
import org.junit.Test;
import java.util.concurrent.Executors;
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
import static com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked;
import static com.alibaba.testable.core.tool.TestableTool.MOCK_CONTEXT;
import static com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD;
import static org.junit.Assert.assertEquals;
@@ -23,42 +23,42 @@ public class DemoBasicTest {
private DemoBasic demoBasic = new DemoBasic();
public static class Mock {
@MockConstructor
@MockNew
private BlackBox createBlackBox(String text) {
return new BlackBox("mock_" + text);
}
@MockMethod(targetClass = DemoBasic.class)
@MockInvoke(targetClass = DemoBasic.class)
private String innerFunc(String text) {
return "mock_" + text;
}
@MockMethod(targetClass = DemoBasic.class)
@MockInvoke(targetClass = DemoBasic.class)
private String staticFunc() {
return "_MOCK_TAIL";
}
@MockMethod(targetClass = String.class)
@MockInvoke(targetClass = String.class)
private String trim() {
return "trim_string";
}
@MockMethod(targetClass = String.class, targetMethod = "substring")
@MockInvoke(targetClass = String.class, targetMethod = "substring")
private String sub(int i, int j) {
return "sub_string";
}
@MockMethod(targetClass = String.class)
@MockInvoke(targetClass = String.class)
private boolean startsWith(String s) {
return false;
}
@MockMethod(targetClass = BlackBox.class)
@MockInvoke(targetClass = BlackBox.class)
private BlackBox secretBox() {
return new BlackBox("not_secret_box");
}
@MockMethod(targetClass = DemoBasic.class)
@MockInvoke(targetClass = DemoBasic.class)
private String callFromDifferentMethod() {
if ("special_case".equals(MOCK_CONTEXT.get("case"))) {
return "mock_special";
@@ -75,28 +75,28 @@ public class DemoBasicTest {
@Test
public void should_mock_new_object() {
assertEquals("mock_something", demoBasic.newFunc());
verify("createBlackBox").with("something");
verifyInvoked("createBlackBox").with("something");
}
@Test
public void should_mock_member_method() throws Exception {
assertEquals("{ \"res\": \"mock_hello_MOCK_TAIL\"}", demoBasic.outerFunc("hello"));
verify("innerFunc").with("hello");
verify("staticFunc").with();
verifyInvoked("innerFunc").with("hello");
verifyInvoked("staticFunc").with();
}
@Test
public void should_mock_common_method() {
assertEquals("trim_string__sub_string__false", demoBasic.commonFunc());
verify("trim").withTimes(1);
verify("sub").withTimes(1);
verify("startsWith").withTimes(1);
verifyInvoked("trim").withTimes(1);
verifyInvoked("sub").withTimes(1);
verifyInvoked("startsWith").withTimes(1);
}
@Test
public void should_mock_static_method() {
assertEquals("not_secret_box", demoBasic.getBox().get());
verify("secretBox").withTimes(1);
verifyInvoked("secretBox").withTimes(1);
}
@Test
@@ -106,7 +106,7 @@ public class DemoBasicTest {
// asynchronous
assertEquals("mock_one_mock_others",
Executors.newSingleThreadExecutor().submit(() -> demoBasic.callerOne() + "_" + demoBasic.callerTwo()).get());
verify("callFromDifferentMethod").withTimes(4);
verifyInvoked("callFromDifferentMethod").withTimes(4);
}
@Test
@@ -116,7 +116,7 @@ public class DemoBasicTest {
assertEquals("mock_special", demoBasic.callerOne());
// asynchronous
assertEquals("mock_special", Executors.newSingleThreadExecutor().submit(() -> demoBasic.callerOne()).get());
verify("callFromDifferentMethod").withTimes(2);
verifyInvoked("callFromDifferentMethod").withTimes(2);
}
}

View File

@@ -3,7 +3,7 @@ package com.alibaba.testable.demo;
import android.content.Intent;
import android.util.Log;
import com.alibaba.testable.core.annotation.MockMethod;
import com.alibaba.testable.core.annotation.MockInvoke;
import org.junit.Before;
import org.junit.Test;
@@ -12,7 +12,7 @@ import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
import static com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked;
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 30)
@@ -21,7 +21,7 @@ public class DemoServiceTest {
private DemoService demoService;
public static class Mock {
@MockMethod(targetClass = Log.class, targetMethod = "d")
@MockInvoke(targetClass = Log.class, targetMethod = "d")
public static int log(String tag, String msg) {
return 0;
}
@@ -38,10 +38,10 @@ public class DemoServiceTest {
intent.setAction("start_foreground");
demoService.onStartCommand(intent, 0, 1);
verify("log").with("DemoService", "start service.");
verifyInvoked("log").with("DemoService", "start service.");
intent.setAction("stop_foreground");
demoService.onStartCommand(intent, 0, 1);
verify("log").with("DemoService", "stop service.");
verifyInvoked("log").with("DemoService", "stop service.");
}
}

View File

@@ -13,8 +13,8 @@ repositories {
dependencies {
testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
testImplementation('com.alibaba.testable:testable-all:0.6.8')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.6.8')
testImplementation('com.alibaba.testable:testable-all:0.7.9')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.7.9')
}
tasks.withType(JavaCompile) {

View File

@@ -11,8 +11,8 @@
<properties>
<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.8</testable.version>
<junit.version>5.9.1</junit.version>
<testable.version>0.7.9</testable.version>
</properties>
<dependencies>
@@ -35,7 +35,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<version>3.0.0-M7</version>
<configuration>
<argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
</configuration>
@@ -56,7 +56,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.6</version>
<version>0.8.8</version>
<executions>
<execution>
<id>prepare-agent</id>

View File

@@ -46,7 +46,9 @@ public class DemoPrivateAccess {
* 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;
return list.stream().reduce((a, s) -> a + s).orElse("")
+ " + " + (str == null ? "null" : str)
+ " + " + i;
}
}

View File

@@ -8,10 +8,10 @@ import com.alibaba.demo.basic.model.mock.Color;
* 演示父类变量引用子类对象时的Mock场景
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
*/
public class DemoInherit {
public class DemoReference {
/**
* call method overridden by sub class via parent class variable
* call method overridden by subclass via parent class variable
*/
public Box putIntoBox() {
Box box = new BlackBox("");
@@ -20,7 +20,7 @@ public class DemoInherit {
}
/**
* call method overridden by sub class via sub class variable
* call method overridden by subclass via subclass variable
*/
public BlackBox putIntoBlackBox() {
BlackBox box = new BlackBox("");
@@ -37,7 +37,7 @@ public class DemoInherit {
}
/**
* call method defined in parent class via sub class variable
* call method defined in parent class via subclass variable
*/
public String getFromBlackBox() {
BlackBox box = new BlackBox("data");
@@ -53,10 +53,18 @@ public class DemoInherit {
}
/**
* call method defined in interface via sub class variable
* call method defined in interface via subclass variable
*/
public String getColorViaBox() {
BlackBox box = new BlackBox("");
return box.getColor();
}
/**
* call method defined in interface via subclass variable
*/
public String getColorIdxViaColor() {
Color color = new BlackBox("");
return color.getColorIndex();
}
}

View File

@@ -0,0 +1,7 @@
package com.alibaba.demo.basic.model.mock;
public interface BasicColor {
String getColorIndex();
}

View File

@@ -20,4 +20,8 @@ public class BlackBox extends Box implements Color {
return "black";
}
@Override
public String getColorIndex() {
return "idx";
}
}

View File

@@ -1,6 +1,6 @@
package com.alibaba.demo.basic.model.mock;
public interface Color {
public interface Color extends BasicColor {
String getColor();

View File

@@ -15,6 +15,14 @@ public class Child {
private InnerChild subChild;
/**
* 这是一个私有内部类
* An private inner class
*/
private class InnerChild {
private String secret;
}
/* ---------------------------------------
以下Getters/Setters方法仅为便于功能演示而添加
并非OmniConstructor或OmniAccessor功能所需
@@ -36,12 +44,4 @@ public class Child {
this.subChild = subChild;
}
/**
* 这是一个私有内部类
* An private inner class
*/
private class InnerChild {
private String secret;
}
}

View File

@@ -0,0 +1,25 @@
package com.alibaba.demo.inherit;
/**
* 演示使用@MockContainer注解实现Mock容器类的多重继承
* Demonstrate multiple inherit of mock container class with @MockContainer annotation
*/
public class DemoMultipleInherit {
private String prefix() {
return "ori_";
}
private String middle() {
return "gin";
}
private String suffix(int some, String more, Object[] parameters) {
return "_al";
}
public String entry() {
return prefix() + middle() + suffix(0, null, null);
}
}

View File

@@ -0,0 +1,21 @@
package com.alibaba.demo.inherit;
/**
* 演示使用extends关键字实现Mock容器类的继承
* Demonstrate inherit of mock container class
*/
public class DemoSingleInherit {
private String prefix() {
return "re_";
}
private String suffix() {
return "_al";
}
public String entry(String word) {
return prefix() + word + suffix();
}
}

View File

@@ -0,0 +1,42 @@
package com.alibaba.demo.lambda;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* @author jim
*/
public interface BaseDemo {
default void blackHole(Object... ignore) {}
default void consumesRun(Runnable r) {
r.run();
}
default void consumesFunction0(Runnable f) {
f.run();
}
default <T> void consumesFunction1(Consumer<T> f) {
f.accept(null);
}
default <T, R> void consumesFunction2(Function<T, R> f) {
f.apply(null);
}
default <T1, T2, R> void consumesFunction3(BiFunction<T1, T2, R> f) {
f.apply(null, null);
}
default <T> void consumesSupplier(Supplier<T> supplier) {
supplier.get();
}
default <T, R> void consumesTwoFunction2(Function<T, R> f1, Function<T, R> f2) {
f1.apply(null);
f2.apply(null);
}
}

View File

@@ -0,0 +1,121 @@
package com.alibaba.demo.lambda;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author jim
*/
@SuppressWarnings("SimplifyStreamApiCallChains")
public class CollectionListCodeDemo implements BaseDemo {
public void list() {
List<User> userList = getUsers();
double d = userList.stream()
.map(v -> v)
.map(v -> Function.<User>identity().apply(v))
.map(User::getId)
.mapToDouble(Double::valueOf)
.reduce(0, Double::sum);
//.reduce(0, (v1, v2) -> v1 + v2);
blackHole(d);
blackHole(userList.stream()
.map(User::getId)
.mapToLong(Long::valueOf)
.reduce(0, Long::sum));
blackHole(userList.stream()
.map(User::getId)
.mapToInt(Long::intValue)
.reduce(0, Integer::sum));
Map<Long, User> map = userList.stream()
.filter(User::isActive)
.collect(Collectors.toMap(User::getId, Function.identity(), (v1, v2) -> v1));
blackHole(map);
List<User> l1 = userList.stream()
.collect(Collectors.toList())
.stream()
.collect(Collectors.toSet())
.stream()
.collect(Collectors.groupingBy(User::getName))
.entrySet()
.stream()
.filter(v -> v.getValue().stream().anyMatch(User::isActive))
.map(Map.Entry::getValue)
.flatMap(Collection::parallelStream)
.collect(Collectors.groupingBy(User::getId))
.values()
.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
blackHole(l1);
userList.stream()
.map(User::isActive)
.reduce(Boolean::logicalAnd)
.ifPresent(this::blackHole);
}
private List<User> getUsers() {
List<User> userList = new ArrayList<>();
for (int i=0; i<10; i++) {
userList.add(getUser(i));
}
return userList;
}
private User getUser(int index) {
User u1 = new User();
u1.id = (long)index;
u1.name = "u" + index;
u1.age = index;
u1.active = true;
return u1;
}
private static class User {
private Long id;
private String name;
private int age;
private boolean active;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
}
}

View File

@@ -0,0 +1,9 @@
package com.alibaba.demo.lambda;
/**
* @author jim
*/
@FunctionalInterface
public interface Function1Throwable<T, R> {
R apply(T t) throws Throwable;
}

View File

@@ -0,0 +1,86 @@
package com.alibaba.demo.lambda;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* @author jim
*/
public class InvokeInterfaceDemo implements BaseDemo {
public void collectionInterfaceDefaultOrStatic() {
blackHole(collectionStreamTest());
blackHole(arraysStreamTest());
}
public void interfaceDefault() {
ILambda l = new LambdaFoo();
consumesRun(l::run);
consumesFunction1(l::function1);
}
public void interfaceStatic() {
consumesRun(ILambda::staticRun);
consumesFunction1(ILambda::staticFunction1);
}
public Object collectionStreamTest() {
List<List<String>> testList = new ArrayList<>();
List<String> fooList = new ArrayList<>();
fooList.add("123");
fooList.add("456");
testList.add(fooList);
return testList.stream()
//.flatMap(v -> v.stream())
.flatMap(Collection::stream)
.map(Double::valueOf)
.map(BigDecimal::new)
.reduce(BigDecimal::add);
}
public Object arraysStreamTest() {
List<String[]> zz = new ArrayList<>();
zz.add(new String[]{"1", "2", "3"});
return zz.stream()
.flatMap(Arrays::stream)
.map(Double::valueOf)
.map(BigDecimal::new)
.reduce(BigDecimal::add);
}
public void objectStaticMethodReference() {
List<Boolean> f = new ArrayList<>();
f.add(false);
f.add(false);
f.add(false);
blackHole(f.stream()
.reduce(Boolean::logicalAnd)
.get()
);
}
public interface ILambda {
default void run() {
}
default void function1(String s) {
}
static void staticRun() {
}
static void staticFunction1(String s) {
}
}
public static class LambdaFoo implements ILambda {
}
}

View File

@@ -0,0 +1,243 @@
package com.alibaba.demo.lambda;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* @author jimca
*/
@SuppressWarnings({"WrapperTypeMayBePrimitive", "ResultOfMethodCallIgnored", "MismatchedReadAndWriteOfArray", "unused"})
public class InvokeVirtualDemo {
public void string1() {
String s = "";
consumesFunction2(s::contains);
}
public void string2() {
String s = "";
consumesFunction2(s::charAt);
}
public void string3() {
String s = "";
consumesFunction0(s::notify);
}
public void byte1() {
Byte s = 1;
consumesSupplier(s::floatValue);
}
public void byte2() {
Byte s = 1;
consumesFunction2(s::compareTo);
}
public void byte3() {
Byte s = 1;
consumesFunction0(s::notify);
}
public void char1() {
Character s = 1;
consumesFunction0(s::toString);
}
public void char2() {
Character s = 1;
consumesFunction2(s::compareTo);
}
public void char3() {
Character s = 1;
consumesFunction0(s::notify);
}
public void short1() {
Short s = 1;
consumesFunction0(s::toString);
}
public void short2() {
Short s = 1;
consumesFunction2(s::compareTo);
}
public void short3() {
Short s = 1;
consumesFunction0(s::notify);
}
public void int1() {
Integer s = 1;
consumesFunction0(s::toString);
}
public void int2() {
Integer s = 1;
consumesFunction2(s::compareTo);
}
public void int3() {
Integer s = 1;
consumesFunction0(s::notify);
}
public void long1() {
Long s = 1L;
consumesFunction0(s::toString);
}
public void long2() {
Long s = 1L;
consumesFunction2(s::compareTo);
}
public void long3() {
Long s = 1L;
consumesFunction0(s::notify);
}
public void float1() {
Float s = 1f;
consumesFunction0(s::toString);
}
public void float2() {
Float s = 1f;
consumesFunction2(s::compareTo);
}
public void float3() {
Float s = 1f;
consumesFunction0(s::notify);
}
public void double1() {
Double s = 1d;
consumesFunction0(s::toString);
}
public void double2() {
Double s = 1d;
consumesFunction2(s::compareTo);
}
public void double3() {
Double s = 1d;
consumesFunction0(s::notify);
}
public void bool1() {
Boolean s = true;
consumesFunction0(s::toString);
}
public void bool2() {
Boolean s = true;
consumesFunction2(s::compareTo);
}
public void bool3() {
Boolean s = true;
consumesFunction0(s::notify);
}
public void stringArray1() {
String[] array = new String[]{""};
consumesFunction0(array::toString);
}
public void stringArray2() {
String[] array = new String[]{""};
consumesFunction2(array::equals);
}
public void stringArray3() {
String[] array = new String[]{""};
consumesFunction0(array::notify);
}
public void intArray1() {
int[] array = new int[]{1};
consumesFunction0(array::toString);
}
public void intArray2() {
int[] array = new int[]{1};
consumesFunction2(array::equals);
}
public void intArray3() {
int[] array = new int[]{1};
consumesFunction0(array::notify);
}
public void mul() {
String s = "";
consumesTwoFunction2(s::contains, s::contains);
}
public void externalClass() {
LambdaDemo lambdaDemo = new LambdaDemo();
consumesFunction0(lambdaDemo::methodReference0);
}
public void interClass() {
A a = new A();
consumesFunction2(a::m1);
consumesFunction2(a::m2);
}
public void function3() {
InvokeVirtualDemo invokeVirtualDemo = new InvokeVirtualDemo();
consumesFunction3(invokeVirtualDemo::f3);
}
public Boolean f3(String s1, Long l) {
return false;
}
private void consumesFunction0(Runnable f) {
f.run();
}
private <T> void consumesFunction1(Consumer<T> f) {
f.accept(null);
}
private <T, R> void consumesFunction2(Function<T, R> f) {
f.apply(null);
}
private <T1, T2, R> void consumesFunction3(BiFunction<T1, T2, R> f) {
f.apply(null, null);
}
private <T> void consumesSupplier(Supplier<T> supplier) {
supplier.get();
}
private <T, R> void consumesTwoFunction2(Function<T, R> f1, Function<T, R> f2) {
f1.apply(null);
f2.apply(null);
}
public static class A {
public String m1(int i) {
return "";
}
public String m2(Integer i) {
return "";
}
}
}

View File

@@ -0,0 +1,218 @@
package com.alibaba.demo.lambda;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
/**
* @author jim
*/
@SuppressWarnings("unused")
public class LambdaDemo {
public void methodReference() {
consumesRun(this::run);
}
private void consumesRun(Runnable r) {
r.run();
}
private void run() {
blackHole();
}
public String methodReference0() {
return consumes0(this::function0);
}
private String consumes0(Supplier<String> function0) {
return function0.get();
}
private String function0() {
return "Hello";
}
public String methodReference1() {
return consumes1(this::function1);
}
private String consumes1(Function<Integer, String> function) {
return function.apply(1);
}
private String function1(Integer i) {
return String.valueOf(i);
}
public String methodReferenceThrows() {
return consumesThrows(this::function1Throwable);
}
private String consumesThrows(Function1Throwable<Integer, String> function) {
try {
return function.apply(1);
}catch (Throwable e) {
e.printStackTrace();
}
return null;
}
@SuppressWarnings("RedundantThrows")
private String function1Throwable(Integer i) throws Throwable{
return String.valueOf(i);
}
public String methodReference2() {
return consumes2(this::function2);
}
private String consumes2(BiFunction<Integer, Double, String> function) {
return function.apply(1, .2);
}
private String function2(Integer i, Double d) {
return i + String.valueOf(d);
}
public String staticMethodReference1() {
return consumes1(StaticMethod::function1);
}
public String staticMethodReference2() {
return consumes2(StaticMethod::function2);
}
public void lambdaRun() {
consumes(() -> System.out.println("lambdaRun"));
}
private void consumes(Runnable o) {
o.run();
}
public void methodReferenceNew() {
Object o = consumes(Object::new);
blackHole(o);
}
private <T> T consumes(Supplier<T> s) {
return s.get();
}
private void blackHole(Object... ignore) {}
public void array() {
Function<Boolean[], Boolean[]> arrayBooleanFunction = this::arrayBooleanFunction;
Function<boolean[], boolean[]> arrayBooleanFunction1 = this::arrayBoolFunction;
Function<Byte[], Byte[]> byteFunction = this::arrayByteFunction;
Function<byte[], byte[]> byteFunction1 = this::arrayByteFunction;
Function<Character[], Character[]> charFunction = this::arrayCharFunction;
Function<char[], char[]> charFunction1 = this::arrayCharFunction;
Function<Short[], Short[]> shortFunction = this::arrayShortFunction;
Function<short[], short[]> shortFunction1 = this::arrayShortFunction;
Function<int[], int[]> intFunction = this::arrayIntFunction;
Function<Integer[], Integer[]> intFunction1 = this::arrayIntegerFunction;
Function<long[], long[]> longFunction = this::arrayLongFunction;
Function<Long[], Long[]> longFunction1 = this::arrayLongFunction;
Function<Float[], Float[]> floatFunction = this::arrayFloatFunction;
Function<float[], float[]> floatFunction1 = this::arrayFloatFunction;
Function<Double[], Double[]> doubleFunction = this::arrayDoubleFunction;
Function<double[], double[]> doubleFunction1 = this::arrayDoubleFunction;
blackHole(arrayBooleanFunction, arrayBooleanFunction1,
byteFunction, byteFunction1, charFunction, charFunction1, shortFunction, shortFunction1,
intFunction, intFunction1, longFunction, longFunction1, floatFunction, floatFunction1, doubleFunction,
doubleFunction1
);
}
private int[] arrayIntFunction(int[] arg) {
return arg;
}
private Integer[] arrayIntegerFunction(Integer[] arg) {
return arg;
}
private boolean[] arrayBoolFunction(boolean[] arg) {
return arg;
}
private Boolean[] arrayBooleanFunction(Boolean[] arg) {
return arg;
}
private byte[] arrayByteFunction(byte[] arg) {
return arg;
}
private Byte[] arrayByteFunction(Byte[] arg) {
return arg;
}
private char[] arrayCharFunction(char[] arg) {
return arg;
}
private Character[] arrayCharFunction(Character[] arg) {
return arg;
}
private short[] arrayShortFunction(short[] arg) {
return arg;
}
private Short[] arrayShortFunction(Short[] arg) {
return arg;
}
private long[] arrayLongFunction(long[] arg) {
return arg;
}
private Long[] arrayLongFunction(Long[] arg) {
return arg;
}
private float[] arrayFloatFunction(float[] arg) {
return arg;
}
private Float[] arrayFloatFunction(Float[] arg) {
return arg;
}
private double[] arrayDoubleFunction(double[] arg) {
return arg;
}
private Double[] arrayDoubleFunction(Double[] arg) {
return arg;
}
public void generic() {
Function<?, ?> genericFunction = this::genericFunction;
blackHole(genericFunction);
}
public <T, R> R genericFunction(T arg) {
//noinspection unchecked
return (R)arg;
}
private void collects() {
long l = Stream.of("1", "2", "3")
.filter(v -> !"2".equals(v))
.map(Long::parseLong)
.peek(this::blackHole)
.map(v -> new ArrayList<Long>(){{add(v);}})
.flatMap(Collection::stream)
.mapToLong(Long::valueOf)
.sum();
blackHole(l);
}
}

View File

@@ -0,0 +1,33 @@
package com.alibaba.demo.lambda;
/**
* @author jim
*/
public class StaticInstanceReference implements BaseDemo {
private static final StaticClassA STATIC_CLASS_A = new StaticClassA();
public void staticMethodReference() {
consumesRun(STATIC_CLASS_A::doIt);
consumesFunction1(STATIC_CLASS_A::function1);
consumesFunction2(STATIC_CLASS_A::function2);
consumesFunction3(STATIC_CLASS_A::function3);
}
public static class StaticClassA {
public void doIt() {
}
public void function1(String s) {
}
public Integer function2(String s) {
return 1;
}
public Integer function3(String s, Double d) {
return 1;
}
}
}

View File

@@ -0,0 +1,15 @@
package com.alibaba.demo.lambda;
/**
* @author jim
*/
public class StaticMethod {
public static String function1(Integer i) {
return "static" + i;
}
public static String function2(Integer i, Double d) {
return "static" + i + d;
}
}

View File

@@ -1,26 +1,26 @@
package com.alibaba.demo.association;
import com.alibaba.testable.core.annotation.MockMethod;
import com.alibaba.testable.core.annotation.MockInvoke;
import com.alibaba.testable.core.model.MockScope;
class CookerServiceMock {
@MockMethod(targetClass = CookerService.class)
@MockInvoke(targetClass = CookerService.class)
public static String hireSandwichCooker() {
return "Fake-Sandwich-Cooker";
}
@MockMethod(targetClass = CookerService.class, scope = MockScope.ASSOCIATED)
@MockInvoke(targetClass = CookerService.class, scope = MockScope.ASSOCIATED)
public static String hireHamburgerCooker() {
return "Fake-Hamburger-Cooker";
}
@MockMethod(targetClass = CookerService.class)
@MockInvoke(targetClass = CookerService.class)
private String cookSandwich() {
return "Faked-Sandwich";
}
@MockMethod(targetClass = CookerService.class, scope = MockScope.ASSOCIATED)
@MockInvoke(targetClass = CookerService.class, scope = MockScope.ASSOCIATED)
private String cookHamburger() {
return "Faked-Hamburger";
}

View File

@@ -1,94 +0,0 @@
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 org.junit.jupiter.api.Test;
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* 演示父类变量引用子类对象时的Mock场景
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
*/
class DemoInheritTest {
private DemoInherit demoInherit = new DemoInherit();
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 = "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 = "getColor")
private String get_color_from_color(Color self) {
return "color_from_color";
}
@MockMethod(targetMethod = "getColor")
private String get_color_from_blackbox(BlackBox self) {
return "color_from_blackbox";
}
}
@Test
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_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_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_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_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_mock_call_interface_method_by_sub_class_object() {
String color = demoInherit.getColorViaBox();
verify("get_color_from_blackbox").withTimes(1);
assertEquals("color_from_blackbox", color);
}
}

View File

@@ -1,6 +1,6 @@
package com.alibaba.demo.basic;
import com.alibaba.testable.core.annotation.MockMethod;
import com.alibaba.testable.core.annotation.MockInvoke;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -12,7 +12,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
class DemoInnerClassTest {
public static class Mock {
@MockMethod(targetClass = DemoInnerClass.class)
@MockInvoke(targetClass = DemoInnerClass.class)
String methodToBeMock() {
return "MockedCall";
}

View File

@@ -1,12 +1,12 @@
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.annotation.MockInvoke;
import com.alibaba.testable.core.error.VerifyFailedError;
import org.junit.jupiter.api.Test;
import static com.alibaba.testable.core.matcher.InvokeMatcher.*;
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
import static com.alibaba.testable.core.matcher.InvocationMatcher.*;
import static com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked;
import static org.junit.jupiter.api.Assertions.fail;
/**
@@ -18,62 +18,62 @@ class DemoMatcherTest {
private DemoMatcher demoMatcher = new DemoMatcher();
public static class Mock {
@MockMethod(targetMethod = "methodToBeMocked")
@MockInvoke(targetMethod = "methodToBeMocked")
private void methodWithoutArgument(DemoMatcher self) {}
@MockMethod(targetMethod = "methodToBeMocked")
@MockInvoke(targetMethod = "methodToBeMocked")
private void methodWithArguments(DemoMatcher self, Object a1, Object a2) {}
@MockMethod(targetMethod = "methodToBeMocked")
@MockInvoke(targetMethod = "methodToBeMocked")
private void methodWithArrayArgument(DemoMatcher self, Object[] a) {}
}
@Test
void should_match_no_argument() {
demoMatcher.callMethodWithoutArgument();
verify("methodWithoutArgument").withTimes(1);
verifyInvoked("methodWithoutArgument").withTimes(1);
demoMatcher.callMethodWithoutArgument();
verify("methodWithoutArgument").withTimes(2);
verifyInvoked("methodWithoutArgument").withTimes(2);
}
@Test
void should_match_number_arguments() {
demoMatcher.callMethodWithNumberArguments();
verify("methodWithArguments").without(anyString(), 2);
verify("methodWithArguments").withInOrder(anyInt(), 2);
verify("methodWithArguments").withInOrder(anyLong(), anyNumber());
verify("methodWithArguments").with(1.0, anyMapOf(Integer.class, Float.class));
verify("methodWithArguments").with(anyList(), anySetOf(Float.class));
verify("methodWithArguments").with(anyList(), anyListOf(Float.class));
verify("methodWithArrayArgument").with(anyArrayOf(Long.class));
verify("methodWithArrayArgument").with(anyArray());
verifyInvoked("methodWithArguments").without(anyString(), 2);
verifyInvoked("methodWithArguments").withInOrder(anyInt(), 2);
verifyInvoked("methodWithArguments").withInOrder(anyLong(), anyNumber());
verifyInvoked("methodWithArguments").with(1.0, anyMapOf(Integer.class, Float.class));
verifyInvoked("methodWithArguments").with(anyList(), anySetOf(Float.class));
verifyInvoked("methodWithArguments").with(anyList(), anyListOf(Float.class));
verifyInvoked("methodWithArrayArgument").with(anyArrayOf(Long.class));
verifyInvoked("methodWithArrayArgument").with(anyArray());
}
@Test
void should_match_string_arguments() {
demoMatcher.callMethodWithStringArgument();
verify("methodWithArguments").with(startsWith("he"), endsWith("ld"));
verify("methodWithArguments").with(contains("stab"), matches("m.[cd]k"));
verify("methodWithArrayArgument").with(anyArrayOf(String.class));
verifyInvoked("methodWithArguments").with(startsWith("he"), endsWith("ld"));
verifyInvoked("methodWithArguments").with(contains("stab"), matches("m.[cd]k"));
verifyInvoked("methodWithArrayArgument").with(anyArrayOf(String.class));
}
@Test
void should_match_object_arguments() {
demoMatcher.callMethodWithObjectArgument();
verify("methodWithArguments").withInOrder(any(BlackBox.class), any(BlackBox.class));
verify("methodWithArguments").withInOrder(nullable(BlackBox.class), nullable(BlackBox.class));
verify("methodWithArguments").withInOrder(isNull(), notNull());
verifyInvoked("methodWithArguments").withInOrder(any(BlackBox.class), any(BlackBox.class));
verifyInvoked("methodWithArguments").withInOrder(nullable(BlackBox.class), nullable(BlackBox.class));
verifyInvoked("methodWithArguments").withInOrder(isNull(), notNull());
}
@Test
void should_match_with_times() {
demoMatcher.callMethodWithNumberArguments();
verify("methodWithArguments").with(anyNumber(), any()).times(3);
verifyInvoked("methodWithArguments").with(anyNumber(), any()).times(3);
demoMatcher.callMethodWithNumberArguments();
boolean gotError = false;
try {
verify("methodWithArguments").with(anyNumber(), any()).times(4);
verifyInvoked("methodWithArguments").with(anyNumber(), any()).times(4);
} catch (VerifyFailedError e) {
gotError = true;
}

View File

@@ -1,13 +1,13 @@
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 com.alibaba.testable.core.annotation.MockNew;
import com.alibaba.testable.core.annotation.MockInvoke;
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.matcher.InvocationVerifier.verifyInvoked;
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;
@@ -21,42 +21,42 @@ class DemoMockTest {
private DemoMock demoMock = new DemoMock();
public static class Mock {
@MockConstructor
@MockNew
private BlackBox createBlackBox(String text) {
return new BlackBox("mock_" + text);
}
@MockMethod(targetClass = DemoMock.class)
@MockInvoke(targetClass = DemoMock.class)
private String innerFunc(String text) {
return "mock_" + text;
}
@MockMethod(targetClass = DemoMock.class)
@MockInvoke(targetClass = DemoMock.class)
private String staticFunc() {
return "_MOCK_TAIL";
}
@MockMethod(targetClass = String.class)
@MockInvoke(targetClass = String.class)
private String trim() {
return "trim_string";
}
@MockMethod(targetClass = String.class, targetMethod = "substring")
@MockInvoke(targetClass = String.class, targetMethod = "substring")
private String sub(int i, int j) {
return "sub_string";
}
@MockMethod(targetClass = String.class)
@MockInvoke(targetClass = String.class)
private boolean startsWith(String s) {
return false;
}
@MockMethod(targetClass = BlackBox.class)
@MockInvoke(targetClass = BlackBox.class)
private BlackBox secretBox() {
return new BlackBox("not_secret_box");
}
@MockMethod(targetClass = DemoMock.class)
@MockInvoke(targetClass = DemoMock.class)
private String callFromDifferentMethod() {
if ("special_case".equals(MOCK_CONTEXT.get("case"))) {
return "mock_special";
@@ -73,28 +73,28 @@ class DemoMockTest {
@Test
void should_mock_new_object() {
assertEquals("mock_something", demoMock.newFunc());
verify("createBlackBox").with("something");
verifyInvoked("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();
verifyInvoked("innerFunc").with("hello");
verifyInvoked("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);
verifyInvoked("trim").withTimes(1);
verifyInvoked("sub").withTimes(1);
verifyInvoked("startsWith").withTimes(1);
}
@Test
void should_mock_static_method() {
assertEquals("not_secret_box", demoMock.getBox().get());
verify("secretBox").withTimes(1);
verifyInvoked("secretBox").withTimes(1);
}
@Test
@@ -104,7 +104,7 @@ class DemoMockTest {
// asynchronous
assertEquals("mock_one_mock_others",
Executors.newSingleThreadExecutor().submit(() -> demoMock.callerOne() + "_" + demoMock.callerTwo()).get());
verify("callFromDifferentMethod").withTimes(4);
verifyInvoked("callFromDifferentMethod").withTimes(4);
}
@Test
@@ -114,7 +114,7 @@ class DemoMockTest {
assertEquals("mock_special", demoMock.callerOne());
// asynchronous
assertEquals("mock_special", Executors.newSingleThreadExecutor().submit(() -> demoMock.callerOne()).get());
verify("callFromDifferentMethod").withTimes(2);
verifyInvoked("callFromDifferentMethod").withTimes(2);
}
}

View File

@@ -52,7 +52,13 @@ class DemoPrivateAccessorTest {
void should_use_null_parameter() {
set(demoPrivateAccess, "pi", null);
assertNull(get(demoPrivateAccess, "pi"));
assertEquals("null + 1", invokeStatic(DemoPrivateAccess.class, "privateStaticFuncWithArgs", null, 1));
List<String> list = new ArrayList<String>() {{ add("a"); add("b"); add("c"); }};
String value = invoke(demoPrivateAccess, "privateFuncWithArgs", list, null, 0);
assertEquals("abc + null + 0", value);
value = invokeStatic(DemoPrivateAccess.class, "privateStaticFuncWithArgs", null, 1);
assertEquals("null + 1", value);
}
}

View File

@@ -0,0 +1,115 @@
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.MockInvoke;
import org.junit.jupiter.api.Test;
import static com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* 演示父类变量引用子类对象时的Mock场景
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
*/
class DemoReferenceTest {
private DemoReference demoReference = new DemoReference();
public static class Mock {
/**
* 当需要引用原对象时,不加`targetClass`参数将原类型作为Mock方法的首个参数建议将参数命名为`self`
* When method body need to refer the invoked object, skip the use `targetClass` parameter,
* and add a parameter `self` with target class type before other paramter of the target method
*/
@MockInvoke(targetMethod = "put")
private void put_into_box(Box self, String something) {
self.put("put_" + something + "_into_box");
}
@MockInvoke(targetMethod = "put")
private void put_into_blackbox(BlackBox self, String something) {
self.put("put_" + something + "_into_blackbox");
}
/**
* 当不需要引用原对象时,将目标类型作为`@MockInvoke`注解的`targetClass`参数值即可
* When the invoked object is not used in the mock method, set `targetClass` value to the type who owns the mocked method
*/
@MockInvoke(targetClass = Box.class, targetMethod = "get")
private String get_from_box() {
return "get_from_box";
}
@MockInvoke(targetClass = BlackBox.class, targetMethod = "get")
private String get_from_blackbox() {
return "get_from_blackbox";
}
@MockInvoke(targetClass = Color.class, targetMethod = "getColor")
private String get_color_from_color() {
return "color_from_color";
}
@MockInvoke(targetClass = BlackBox.class, targetMethod = "getColor")
private String get_color_from_blackbox() {
return "color_from_blackbox";
}
@MockInvoke(targetClass = Color.class, targetMethod = "getColorIndex")
private String get_colorIdx_from_color() {
return "colorIdx_from_color";
}
}
@Test
void should_mock_call_sub_object_method_by_parent_object() {
BlackBox box = (BlackBox) demoReference.putIntoBox();
verifyInvoked("put_into_box").withTimes(1);
assertEquals("put_data_into_box", box.get());
}
@Test
void should_mock_call_sub_object_method_by_sub_object() {
BlackBox box = demoReference.putIntoBlackBox();
verifyInvoked("put_into_blackbox").withTimes(1);
assertEquals("put_data_into_blackbox", box.get());
}
@Test
void should_mock_call_parent_object_method_by_parent_object() {
String content = demoReference.getFromBox();
verifyInvoked("get_from_box").withTimes(1);
assertEquals("get_from_box", content);
}
@Test
void should_mock_call_parent_object_method_by_sub_object() {
String content = demoReference.getFromBlackBox();
verifyInvoked("get_from_blackbox").withTimes(1);
assertEquals("get_from_blackbox", content);
}
@Test
void should_mock_call_interface_method_by_interface_object() {
String color = demoReference.getColorViaColor();
verifyInvoked("get_color_from_color").withTimes(1);
assertEquals("color_from_color", color);
}
@Test
void should_mock_call_interface_method_by_sub_class_object() {
String color = demoReference.getColorViaBox();
verifyInvoked("get_color_from_blackbox").withTimes(1);
assertEquals("color_from_blackbox", color);
}
@Test
void should_mock_call_interface_method_by_sub_interface_object() {
String colorIdx = demoReference.getColorIdxViaColor();
verifyInvoked("get_colorIdx_from_color").withTimes(1);
assertEquals("colorIdx_from_color", colorIdx);
}
}

View File

@@ -1,8 +1,7 @@
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 com.alibaba.testable.core.annotation.MockNew;
import com.alibaba.testable.core.annotation.MockInvoke;
import org.junit.jupiter.api.Test;
import java.util.*;
@@ -21,24 +20,24 @@ class DemoTemplateTest {
/* 第一种写法:使用泛型定义 */
/* First solution: use generics type */
@MockMethod
@MockInvoke
private <T> List<T> getList(DemoTemplate self, T value) {
return new ArrayList<T>() {{ add((T)(value.toString() + "_mock_list")); }};
}
@MockMethod
@MockInvoke
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
@MockNew
private <T> HashSet<T> newHashSet() {
HashSet<T> set = new HashSet<>();
set.add((T)"insert_mock");
return set;
}
@MockMethod
@MockInvoke
private <E> boolean add(Set s, E e) {
s.add(e.toString() + "_mocked");
return true;
@@ -47,24 +46,24 @@ class DemoTemplateTest {
/* 第二种写法使用Object类型 */
/* Second solution: use object type */
//@MockMethod
//@MockInvoke
//private List<Object> getList(DemoTemplate self, Object value) {
// return new ArrayList<Object>() {{ add(value.toString() + "_mock_list"); }};
//}
//
//@MockMethod
//@MockInvoke
//private Map<Object, Object> getMap(DemoTemplate self, Object key, Object value) {
// return new HashMap<Object, Object>() {{ put(key, value.toString() + "_mock_map"); }};
//}
//
//@MockConstructor
//@MockNew
//private HashSet newHashSet() {
// HashSet<Object> set = new HashSet<>();
// set.add("insert_mock");
// return set;
//}
//
//@MockMethod
//@MockInvoke
//private boolean add(Set s, Object e) {
// s.add(e.toString() + "_mocked");
// return true;

View File

@@ -0,0 +1,48 @@
package com.alibaba.demo.inherit;
import com.alibaba.testable.core.annotation.MockContainer;
import com.alibaba.testable.core.annotation.MockInvoke;
import org.junit.jupiter.api.Test;
import static com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked;
import static org.junit.jupiter.api.Assertions.*;
/**
* 演示使用@MockContainer注解实现Mock容器类的多重继承
* Demonstrate multiple inherit of mock container class with @MockContainer annotation
*/
class DemoMultipleInheritTest {
private DemoMultipleInherit demoMultipleInherit = new DemoMultipleInherit();
public static class PrefixMock {
@MockInvoke(targetClass = DemoMultipleInherit.class)
private String prefix() {
return "in_";
}
}
public static class SuffixMock {
@MockInvoke(targetClass = DemoMultipleInherit.class)
private String suffix(int some, String more, Object[] parameters) {
return "_it";
}
}
@MockContainer(inherits = { PrefixMock.class, SuffixMock.class })
public static class Mock {
@MockInvoke(targetClass = DemoMultipleInherit.class)
private String middle() {
return "her";
}
}
@Test
public void should_use_mock_method_in_parent_class() {
assertEquals("in_her_it", demoMultipleInherit.entry());
verifyInvoked("prefix").withTimes(1);
verifyInvoked("middle").withTimes(1);
verifyInvoked("suffix").withTimes(1);
}
}

View File

@@ -0,0 +1,38 @@
package com.alibaba.demo.inherit;
import com.alibaba.testable.core.annotation.MockInvoke;
import org.junit.jupiter.api.Test;
import static com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* 演示Mock容器类的继承
* Demonstrate inherit of mock container class with extends keyword
*/
class DemoSingleInheritTest {
private DemoSingleInherit demoSingleInherit = new DemoSingleInherit();
public static class BasicMock {
@MockInvoke(targetClass = DemoSingleInherit.class)
private String suffix() {
return "_ck";
}
}
public static class Mock extends BasicMock {
@MockInvoke(targetClass = DemoSingleInherit.class)
private String prefix() {
return "mo_";
}
}
@Test
public void should_use_mock_method_in_parent_class() {
assertEquals("mo_test_ck", demoSingleInherit.entry("test"));
verifyInvoked("prefix").withTimes(1);
verifyInvoked("suffix").withTimes(1);
}
}

View File

@@ -0,0 +1,25 @@
package com.alibaba.demo.lambda;
import com.alibaba.testable.core.annotation.MockInvoke;
import org.junit.jupiter.api.Test;
/**
* @author jim
*/
public class CollectionListCodeDemoTest {
private final CollectionListCodeDemo instance = new CollectionListCodeDemo();
public static class Mock {
@MockInvoke(targetClass = String.class, targetMethod = "contains")
public boolean mockContains(CharSequence s) {
return false;
}
}
@Test
public void listTest() {
instance.list();
}
}

View File

@@ -0,0 +1,69 @@
package com.alibaba.demo.lambda;
import com.alibaba.testable.core.annotation.MockInvoke;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;
import static com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked;
/**
* @author jim
*/
public class InvokeInterfaceDemoTest {
private final InvokeInterfaceDemo instance = new InvokeInterfaceDemo();
public static class Mock {
@MockInvoke(targetClass = InvokeInterfaceDemo.ILambda.class, targetMethod = "run")
private void mockILambdaRun() {
}
@MockInvoke(targetClass = InvokeInterfaceDemo.ILambda.class, targetMethod = "function1")
private void mockILambdaFunction1(String s) {
}
@SuppressWarnings("unchecked")
@MockInvoke(targetClass = Collection.class, targetMethod = "stream")
<E> Stream<E> mockStream() {
List<E> fooList = new ArrayList<>();
fooList.add((E) "123");
fooList.add((E) "456");
return fooList.stream();
}
@MockInvoke(targetClass = Boolean.class, targetMethod = "logicalAnd")
public static boolean mockLogicalAnd(boolean a, boolean b) {
return false;
}
}
@Test
public void shouldMockCollectionStream() {
instance.collectionInterfaceDefaultOrStatic();
verifyInvoked("mockStream").withTimes(1);
}
@Test
public void shouldMockInterfaceDefault() {
instance.interfaceDefault();
verifyInvoked("mockILambdaRun").withTimes(1);
verifyInvoked("mockILambdaFunction1").withTimes(1);
}
@Test
public void shouldMockObjectStaticMethodReference() {
instance.objectStaticMethodReference();
verifyInvoked("mockLogicalAnd").withTimes(2);
}
@Test
public void shouldMockInterfaceStatic() {
instance.interfaceStatic();
}
}

View File

@@ -0,0 +1,80 @@
package com.alibaba.demo.lambda;
import com.alibaba.testable.core.annotation.MockInvoke;
import org.junit.jupiter.api.Test;
import static com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked;
/**
* @author zcbbpo
*/
public class InvokeVirtualDemoTest {
private final InvokeVirtualDemo lambdaDemo = new InvokeVirtualDemo();
@SuppressWarnings("unused")
public static class Mock {
@MockInvoke(targetClass = String.class, targetMethod = "contains")
public boolean mockContains(CharSequence s) {
return false;
}
@MockInvoke(targetClass = Byte.class, targetMethod = "floatValue")
public float mockFloatValue() {
return 0.1f;
}
@MockInvoke(targetClass = Double.class, targetMethod = "compareTo")
public int mockCompareTo(Double anotherDouble) {
return 1;
}
@MockInvoke(targetClass = LambdaDemo.class, targetMethod = "methodReference0")
public String mockMethodReference0() {
return "";
}
@MockInvoke(targetClass = InvokeVirtualDemo.class, targetMethod = "f3")
public Boolean mockF3(String s1, Long l) {
return true;
}
}
@Test
public void shouldMockString1() {
lambdaDemo.string1();
verifyInvoked("mockContains").withTimes(1);
}
@Test
public void shouldMockByte1() {
lambdaDemo.byte1();
verifyInvoked("mockFloatValue").withTimes(1);
}
@Test
public void shouldMockDouble2() {
lambdaDemo.double2();
verifyInvoked("mockCompareTo").withTimes(1);
}
@Test
public void testMul() {
lambdaDemo.mul();
verifyInvoked("mockContains").withTimes(2);
}
@Test
public void testExternalClass() {
lambdaDemo.externalClass();
verifyInvoked("mockMethodReference0").withTimes(1);
}
@Test
public void testFunction3() {
lambdaDemo.function3();
verifyInvoked("mockF3").withTimes(1);
}
}

View File

@@ -0,0 +1,86 @@
package com.alibaba.demo.lambda;
import com.alibaba.testable.core.annotation.MockInvoke;
import org.junit.jupiter.api.Test;
import static com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* @author zcbbpo
*/
public class LambdaDemoTest {
private final LambdaDemo lambdaDemo = new LambdaDemo();
@SuppressWarnings("unused")
public static class Mock {
@MockInvoke(targetClass = LambdaDemo.class, targetMethod = "run")
private void mockRun() {
}
@MockInvoke(targetClass = LambdaDemo.class)
private String function0() {
return "mock_function0";
}
@MockInvoke(targetClass = LambdaDemo.class)
private String function1(Integer i) {
return "mock_function1";
}
@MockInvoke(targetClass = LambdaDemo.class)
private String function2(Integer i, Double d) {
return "mock_function2";
}
@SuppressWarnings("RedundantThrows")
@MockInvoke(targetClass = LambdaDemo.class)
private String function1Throwable(Integer i) throws Throwable{
return "mock_function1Throwable";
}
@MockInvoke(targetClass = StaticMethod.class, targetMethod = "function1")
public static String staticFunction1(Integer i) {
return "mock_staticFunction1";
}
}
@Test
public void shouldMockRun() {
lambdaDemo.methodReference();
verifyInvoked("mockRun").withTimes(1);
}
@Test
public void shouldMockFunction0() {
String s = lambdaDemo.methodReference0();
assertEquals(s, "mock_function0");
}
@Test
public void shouldMockFunction1() {
String s = lambdaDemo.methodReference1();
assertEquals(s, "mock_function1");
}
@Test
public void shouldMockFunction2() {
String s = lambdaDemo.methodReference2();
assertEquals(s, "mock_function2");
}
@Test
public void shouldMockFunction1Throws() {
String s = lambdaDemo.methodReferenceThrows();
assertEquals(s, "mock_function1Throwable");
}
@Test
public void shouldMockStaticFunction1() {
String s = lambdaDemo.staticMethodReference1();
assertEquals(s, "mock_staticFunction1");
}
}

View File

@@ -0,0 +1,33 @@
package com.alibaba.demo.lambda;
import com.alibaba.testable.core.annotation.MockInvoke;
import org.junit.jupiter.api.Test;
import static com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked;
/**
* @author jim
*/
public class StaticInstanceReferenceTest {
private final StaticInstanceReference instance = new StaticInstanceReference();
public static class Mock {
@MockInvoke(targetClass = StaticInstanceReference.StaticClassA.class, targetMethod = "doIt")
private void mockDoIt() {
}
@MockInvoke(targetClass = StaticInstanceReference.StaticClassA.class)
private Integer function2(String s) {
return 2;
}
}
@Test
public void shouldMockDoIt() {
instance.staticMethodReference();
verifyInvoked("mockDoIt").withTimes(1);
verifyInvoked("function2").withTimes(1);
}
}

View File

@@ -1,10 +1,10 @@
package com.alibaba.demo.one2multi;
import com.alibaba.testable.core.annotation.MockMethod;
import com.alibaba.testable.core.annotation.MockInvoke;
public class ASvcMock {
@MockMethod(targetClass = String.class, targetMethod = "format")
@MockInvoke(targetClass = String.class, targetMethod = "format")
public String a_format(String format, Object... args) {
return "a_mock";
}

View File

@@ -1,10 +1,10 @@
package com.alibaba.demo.one2multi;
import com.alibaba.testable.core.annotation.MockMethod;
import com.alibaba.testable.core.annotation.MockInvoke;
public class BSvcMock {
@MockMethod(targetClass = String.class, targetMethod = "format")
@MockInvoke(targetClass = String.class, targetMethod = "format")
public String b_format(String format, Object... args) {
return "b_mock";
}

View File

@@ -1,10 +1,10 @@
package com.alibaba.demo.one2multi;
import com.alibaba.testable.core.annotation.MockMethod;
import com.alibaba.testable.core.annotation.MockInvoke;
public class CSvcMock {
@MockMethod(targetClass = String.class, targetMethod = "format")
@MockInvoke(targetClass = String.class, targetMethod = "format")
public String c_format(String format, Object... args) {
return "c_mock";
}

View File

@@ -4,7 +4,7 @@ import com.alibaba.testable.core.annotation.MockWith;
import org.junit.jupiter.api.Test;
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
import static com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked;
import static org.junit.jupiter.api.Assertions.assertEquals;
@MockWith
@@ -19,9 +19,9 @@ public class OneToMultiSvcTest {
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);
verifyInvoked("a_format").withTimes(1);
verifyInvoked("b_format").withTimes(1);
verifyInvoked("c_format").withTimes(1);
}
}

View File

@@ -17,8 +17,8 @@ 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.6.8")
testAnnotationProcessor("com.alibaba.testable:testable-processor:0.6.8")
testImplementation("com.alibaba.testable:testable-all:0.7.9")
testAnnotationProcessor("com.alibaba.testable:testable-processor:0.7.9")
}
tasks.withType<KotlinCompile> {

View File

@@ -13,8 +13,8 @@
<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.8</testable.version>
<junit.version>5.9.1</junit.version>
<testable.version>0.7.9</testable.version>
</properties>
<dependencies>
@@ -50,7 +50,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<version>3.0.0-M7</version>
<!-- <configuration>-->
<!-- <argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>-->
<!-- </configuration>-->
@@ -71,7 +71,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.6</version>
<version>0.8.8</version>
<executions>
<execution>
<id>prepare-agent</id>

View File

@@ -8,10 +8,10 @@ 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 {
class DemoReference {
/**
* call method overridden by sub class via parent class variable
* call method overridden by subclass via parent class variable
*/
fun putIntoBox(): Box {
val box: Box = BlackBox("")
@@ -20,7 +20,7 @@ class DemoInherit {
}
/**
* call method overridden by sub class via sub class variable
* call method overridden by subclass via subclass variable
*/
fun putIntoBlackBox(): BlackBox {
val box = BlackBox("")
@@ -38,7 +38,7 @@ class DemoInherit {
}
/**
* call method defined in parent class via sub class variable
* call method defined in parent class via subclass variable
*/
val fromBlackBox: String?
get() {
@@ -56,7 +56,7 @@ class DemoInherit {
}
/**
* call method defined in interface via sub class variable
* call method defined in interface via subclass variable
*/
val colorViaBox: String
get() {

View File

@@ -1,26 +1,26 @@
package com.alibaba.demo.association
import com.alibaba.testable.core.annotation.MockMethod
import com.alibaba.testable.core.annotation.MockInvoke
import com.alibaba.testable.core.model.MockScope
internal class CookerServiceMock {
@MockMethod(targetClass = CookerService::class)
@MockInvoke(targetClass = CookerService::class)
private fun cookSandwich(): String {
return "Faked-Sandwich"
}
@MockMethod(targetClass = CookerService::class, scope = MockScope.ASSOCIATED)
@MockInvoke(targetClass = CookerService::class, scope = MockScope.ASSOCIATED)
private fun cookHamburger(): String {
return "Faked-Hamburger"
}
@MockMethod(targetClass = CookerService::class)
@MockInvoke(targetClass = CookerService::class)
fun hireSandwichCooker(): String {
return "Fake-Sandwich-Cooker"
}
@MockMethod(targetClass = CookerService::class, scope = MockScope.ASSOCIATED)
@MockInvoke(targetClass = CookerService::class, scope = MockScope.ASSOCIATED)
fun hireHamburgerCooker(): String {
return "Fake-Hamburger-Cooker"
}

View File

@@ -1,6 +1,6 @@
package com.alibaba.demo.basic
import com.alibaba.testable.core.annotation.MockMethod
import com.alibaba.testable.core.annotation.MockInvoke
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
@@ -11,7 +11,7 @@ import org.junit.jupiter.api.Test
internal class DemoInnerClassTest {
class Mock {
@MockMethod(targetClass = DemoInnerClass::class)
@MockInvoke(targetClass = DemoInnerClass::class)
fun methodToBeMock(): String {
return "MockedCall"
}

View File

@@ -1,9 +1,9 @@
package com.alibaba.demo.basic
import com.alibaba.testable.core.annotation.MockMethod
import com.alibaba.testable.core.annotation.MockInvoke
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.core.matcher.InvocationMatcher
import com.alibaba.testable.core.matcher.InvocationVerifier
import com.alibaba.demo.basic.model.mock.BlackBox
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
@@ -17,15 +17,15 @@ internal class DemoMatcherTest {
private val demoMatcher = DemoMatcher()
class Mock {
@MockMethod(targetMethod = "methodToBeMocked")
@MockInvoke(targetMethod = "methodToBeMocked")
private fun methodWithoutArgument(self: DemoMatcher) {
}
@MockMethod(targetMethod = "methodToBeMocked")
@MockInvoke(targetMethod = "methodToBeMocked")
private fun methodWithArguments(self: DemoMatcher, a1: Any, a2: Any) {
}
@MockMethod(targetMethod = "methodToBeMocked")
@MockInvoke(targetMethod = "methodToBeMocked")
private fun methodWithArrayArgument(self: DemoMatcher, a: Array<Any>) {
}
}
@@ -33,50 +33,50 @@ internal class DemoMatcherTest {
@Test
fun should_match_no_argument() {
demoMatcher.callMethodWithoutArgument()
InvokeVerifier.verify("methodWithoutArgument").withTimes(1)
InvocationVerifier.verifyInvoked("methodWithoutArgument").withTimes(1)
demoMatcher.callMethodWithoutArgument()
InvokeVerifier.verify("methodWithoutArgument").withTimes(2)
InvocationVerifier.verifyInvoked("methodWithoutArgument").withTimes(2)
}
@Test
fun should_match_number_arguments() {
demoMatcher.callMethodWithNumberArguments()
InvokeVerifier.verify("methodWithArguments").without(InvokeMatcher.anyString(), 2)
InvokeVerifier.verify("methodWithArguments").withInOrder(InvokeMatcher.anyInt(), 2)
InvokeVerifier.verify("methodWithArguments").withInOrder(InvokeMatcher.anyLong(), InvokeMatcher.anyNumber())
InvocationVerifier.verifyInvoked("methodWithArguments").without(InvocationMatcher.anyString(), 2)
InvocationVerifier.verifyInvoked("methodWithArguments").withInOrder(InvocationMatcher.anyInt(), 2)
InvocationVerifier.verifyInvoked("methodWithArguments").withInOrder(InvocationMatcher.anyLong(), InvocationMatcher.anyNumber())
// Note: Must use `::class.javaObjectType` for primary types check in Kotlin
InvokeVerifier.verify("methodWithArguments").with(1.0, InvokeMatcher.anyMapOf(Int::class.javaObjectType, Float::class.javaObjectType)).times(2)
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyList(), InvokeMatcher.anySetOf(Float::class.javaObjectType)).times(2)
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyList(), InvokeMatcher.anyListOf(Float::class.javaObjectType))
InvokeVerifier.verify("methodWithArrayArgument").with(InvokeMatcher.anyArrayOf(Long::class.javaObjectType))
InvokeVerifier.verify("methodWithArrayArgument").with(InvokeMatcher.anyArray())
InvocationVerifier.verifyInvoked("methodWithArguments").with(1.0, InvocationMatcher.anyMapOf(Int::class.javaObjectType, Float::class.javaObjectType)).times(2)
InvocationVerifier.verifyInvoked("methodWithArguments").with(InvocationMatcher.anyList(), InvocationMatcher.anySetOf(Float::class.javaObjectType)).times(2)
InvocationVerifier.verifyInvoked("methodWithArguments").with(InvocationMatcher.anyList(), InvocationMatcher.anyListOf(Float::class.javaObjectType))
InvocationVerifier.verifyInvoked("methodWithArrayArgument").with(InvocationMatcher.anyArrayOf(Long::class.javaObjectType))
InvocationVerifier.verifyInvoked("methodWithArrayArgument").with(InvocationMatcher.anyArray())
}
@Test
fun should_match_string_arguments() {
demoMatcher.callMethodWithStringArgument()
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.startsWith("he"), InvokeMatcher.endsWith("ld"))
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.contains("stab"), InvokeMatcher.matches("m.[cd]k"))
InvokeVerifier.verify("methodWithArrayArgument").with(InvokeMatcher.anyArrayOf(String::class.java))
InvocationVerifier.verifyInvoked("methodWithArguments").with(InvocationMatcher.startsWith("he"), InvocationMatcher.endsWith("ld"))
InvocationVerifier.verifyInvoked("methodWithArguments").with(InvocationMatcher.contains("stab"), InvocationMatcher.matches("m.[cd]k"))
InvocationVerifier.verifyInvoked("methodWithArrayArgument").with(InvocationMatcher.anyArrayOf(String::class.java))
}
@Test
fun should_match_object_arguments() {
demoMatcher.callMethodWithObjectArgument()
InvokeVerifier.verify("methodWithArguments").withInOrder(InvokeMatcher.any(BlackBox::class.java), InvokeMatcher.any(BlackBox::class.java))
InvokeVerifier.verify("methodWithArguments").withInOrder(InvokeMatcher.nullable(BlackBox::class.java), InvokeMatcher.nullable(BlackBox::class.java))
InvokeVerifier.verify("methodWithArguments").withInOrder(InvokeMatcher.isNull(), InvokeMatcher.notNull())
InvocationVerifier.verifyInvoked("methodWithArguments").withInOrder(InvocationMatcher.any(BlackBox::class.java), InvocationMatcher.any(BlackBox::class.java))
InvocationVerifier.verifyInvoked("methodWithArguments").withInOrder(InvocationMatcher.nullable(BlackBox::class.java), InvocationMatcher.nullable(BlackBox::class.java))
InvocationVerifier.verifyInvoked("methodWithArguments").withInOrder(InvocationMatcher.isNull(), InvocationMatcher.notNull())
}
@Test
fun should_match_with_times() {
demoMatcher.callMethodWithNumberArguments()
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyNumber(), InvokeMatcher.any()).times(4)
InvocationVerifier.verifyInvoked("methodWithArguments").with(InvocationMatcher.anyNumber(), InvocationMatcher.any()).times(4)
demoMatcher.callMethodWithNumberArguments()
var gotError = false
try {
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyNumber(), InvokeMatcher.any()).times(5)
InvocationVerifier.verifyInvoked("methodWithArguments").with(InvocationMatcher.anyNumber(), InvocationMatcher.any()).times(5)
} catch (e: VerifyFailedError) {
gotError = true
}

View File

@@ -1,8 +1,8 @@
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.annotation.MockNew
import com.alibaba.testable.core.annotation.MockInvoke
import com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked
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
@@ -20,37 +20,37 @@ internal class DemoMockTest {
private val demoMock = DemoMock()
class Mock {
@MockConstructor
@MockNew
private fun createBlackBox(text: String) = BlackBox("mock_$text")
@MockMethod(targetClass = DemoMock::class)
@MockInvoke(targetClass = DemoMock::class)
private fun innerFunc(text: String) = "mock_$text"
@MockMethod(targetClass = DemoMock::class)
@MockInvoke(targetClass = DemoMock::class)
private fun staticFunc(): String {
return "_MOCK_TAIL";
}
@MockMethod(targetClass = BlackBox::class)
@MockInvoke(targetClass = BlackBox::class)
private fun trim() = "trim_string"
@MockMethod(targetClass = BlackBox::class, targetMethod = "substring")
@MockInvoke(targetClass = BlackBox::class, targetMethod = "substring")
private fun sub(i: Int, j: Int) = "sub_string"
@MockMethod(targetClass = BlackBox::class)
@MockInvoke(targetClass = BlackBox::class)
private fun startsWith(s: String) = false
@MockMethod(targetClass = BlackBox::class)
@MockInvoke(targetClass = BlackBox::class)
private fun secretBox(): BlackBox {
return BlackBox("not_secret_box")
}
@MockMethod(targetClass = ColorBox::class)
@MockInvoke(targetClass = ColorBox::class)
private fun createBox(color: String, box: BlackBox): BlackBox {
return BlackBox("White_${box.get()}")
}
@MockMethod(targetClass = DemoMock::class)
@MockInvoke(targetClass = DemoMock::class)
private fun callFromDifferentMethod(): String {
return if (MOCK_CONTEXT["case"] == "special_case") {
"mock_special"
@@ -66,35 +66,35 @@ internal class DemoMockTest {
@Test
fun should_mock_new_object() {
assertEquals("mock_something", demoMock.newFunc())
verify("createBlackBox").with("something")
verifyInvoked("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()
verifyInvoked("innerFunc").with("hello")
verifyInvoked("staticFunc").with()
}
// @Test
// fun should_mock_method_in_companion_object() {
// assertEquals("CALL_MOCK_TAIL", DemoMock.callStaticFunc())
// verify("staticFunc").with()
// verifyInvoked("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)
verifyInvoked("trim").withTimes(1)
verifyInvoked("sub").withTimes(1)
verifyInvoked("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)
verifyInvoked("secretBox").withTimes(1)
verifyInvoked("createBox").withTimes(1)
}
@Test
@@ -105,11 +105,11 @@ internal class DemoMockTest {
assertEquals("mock_one_mock_others", Executors.newSingleThreadExecutor().submit<String> {
demoMock.callerOne() + "_" + demoMock.callerTwo()
}.get())
verify("callFromDifferentMethod").withTimes(4)
verifyInvoked("callFromDifferentMethod").withTimes(4)
}
@Test
fun should_get_test_case_name() {
fun should_set_mock_context() {
MOCK_CONTEXT["case"] = "special_case"
// synchronous
assertEquals("mock_special", demoMock.callerOne())
@@ -117,7 +117,7 @@ internal class DemoMockTest {
assertEquals("mock_special", Executors.newSingleThreadExecutor().submit<String> {
demoMock.callerOne()
}.get())
verify("callFromDifferentMethod").withTimes(2)
verifyInvoked("callFromDifferentMethod").withTimes(2)
MOCK_CONTEXT.clear()
}
}

View File

@@ -1,7 +1,7 @@
package com.alibaba.demo.basic
import com.alibaba.testable.core.annotation.MockMethod
import com.alibaba.testable.core.matcher.InvokeVerifier
import com.alibaba.testable.core.annotation.MockInvoke
import com.alibaba.testable.core.matcher.InvocationVerifier
import com.alibaba.demo.basic.model.mock.BlackBox
import com.alibaba.demo.basic.model.mock.Box
import com.alibaba.demo.basic.model.mock.Color
@@ -12,37 +12,37 @@ import org.junit.jupiter.api.Test
* 演示父类变量引用子类对象时的Mock场景
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
*/
internal class DemoInheritTest {
internal class DemoReferenceTest {
private val demoInherit = DemoInherit()
private val demoReference = DemoReference()
class Mock {
@MockMethod(targetMethod = "put")
@MockInvoke(targetMethod = "put")
private fun put_into_box(self: Box, something: String) {
self.put("put_" + something + "_into_box")
}
@MockMethod(targetMethod = "put")
@MockInvoke(targetMethod = "put")
private fun put_into_blackbox(self: BlackBox, something: String) {
self.put("put_" + something + "_into_blackbox")
}
@MockMethod(targetMethod = "get")
@MockInvoke(targetMethod = "get")
private fun get_from_box(self: Box): String {
return "get_from_box"
}
@MockMethod(targetMethod = "get")
@MockInvoke(targetMethod = "get")
private fun get_from_blackbox(self: BlackBox): String {
return "get_from_blackbox"
}
@MockMethod(targetMethod = "getColor")
@MockInvoke(targetMethod = "getColor")
private fun get_color_from_color(self: Color): String {
return "color_from_color"
}
@MockMethod(targetMethod = "getColor")
@MockInvoke(targetMethod = "getColor")
private fun get_color_from_blackbox(self: BlackBox): String {
return "color_from_blackbox"
}
@@ -50,43 +50,43 @@ internal class DemoInheritTest {
@Test
fun should_mock_call_sub_object_method_by_parent_object() {
val box = demoInherit.putIntoBox() as BlackBox
InvokeVerifier.verify("put_into_box").withTimes(1)
val box = demoReference.putIntoBox() as BlackBox
InvocationVerifier.verifyInvoked("put_into_box").withTimes(1)
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)
val box = demoReference.putIntoBlackBox()
InvocationVerifier.verifyInvoked("put_into_blackbox").withTimes(1)
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)
val content = demoReference.fromBox
InvocationVerifier.verifyInvoked("get_from_box").withTimes(1)
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)
val content = demoReference.fromBlackBox
InvocationVerifier.verifyInvoked("get_from_blackbox").withTimes(1)
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)
val color = demoReference.colorViaColor
InvocationVerifier.verifyInvoked("get_color_from_color").withTimes(1)
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)
val color = demoReference.colorViaBox
InvocationVerifier.verifyInvoked("get_color_from_blackbox").withTimes(1)
assertEquals("color_from_blackbox", color)
}
}

View File

@@ -1,7 +1,7 @@
package com.alibaba.demo.basic
import com.alibaba.testable.core.annotation.MockConstructor
import com.alibaba.testable.core.annotation.MockMethod
import com.alibaba.testable.core.annotation.MockNew
import com.alibaba.testable.core.annotation.MockInvoke
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
@@ -14,24 +14,24 @@ internal class DemoTemplateTest {
private val demoTemplate = DemoTemplate()
class Mock {
@MockMethod
@MockInvoke
private fun <T> getList(self: DemoTemplate, value: T): List<T> {
return mutableListOf((value.toString() + "_mock_list") as T)
}
@MockMethod
@MockInvoke
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
@MockNew
private fun newHashSet(): HashSet<*> {
val set = HashSet<Any>()
set.add("insert_mock")
return set
}
@MockMethod
@MockInvoke
private fun <E> add(s: MutableSet<E>, e: E): Boolean {
s.add((e.toString() + "_mocked") as E)
return true

View File

@@ -1,14 +1,14 @@
package com.alibaba.demo.java2kotlin
import com.alibaba.testable.core.annotation.MockMethod
import com.alibaba.testable.core.matcher.InvokeVerifier.verify
import com.alibaba.testable.core.annotation.MockInvoke
import com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked
import org.junit.jupiter.api.Test
import java.io.File
class PathDemoTest {
class Mock {
@MockMethod
@MockInvoke
fun exists(f: File): Boolean {
return when (f.absolutePath) {
"/a/b" -> true
@@ -17,7 +17,7 @@ class PathDemoTest {
}
}
@MockMethod
@MockInvoke
fun isDirectory(f: File): Boolean {
return when (f.absolutePath) {
"/a/b/c" -> true
@@ -25,12 +25,12 @@ class PathDemoTest {
}
}
@MockMethod
@MockInvoke
fun delete(f: File): Boolean {
return true
}
@MockMethod
@MockInvoke
fun listFiles(f: File): Array<File>? {
return when (f.absolutePath) {
"/a/b" -> arrayOf(File("/a/b/c"), File("/a/b/d"))
@@ -43,8 +43,8 @@ class PathDemoTest {
@Test
fun should_mock_java_method_invoke_in_kotlin() {
PathDemo.deleteRecursively(File("/a/b/"))
verify("listFiles").withTimes(2)
verify("delete").withTimes(4)
verifyInvoked("listFiles").withTimes(2)
verifyInvoked("delete").withTimes(4)
}
}

View File

@@ -1,10 +1,10 @@
package com.alibaba.demo.one2multi
import com.alibaba.testable.core.annotation.MockMethod
import com.alibaba.testable.core.annotation.MockInvoke
class ASvcMock {
@MockMethod(targetClass = String::class, targetMethod = "format")
@MockInvoke(targetClass = String::class, targetMethod = "format")
fun a_format(format: String, vararg args: Any?): String {
return "a_mock"
}

View File

@@ -1,10 +1,10 @@
package com.alibaba.demo.one2multi
import com.alibaba.testable.core.annotation.MockMethod
import com.alibaba.testable.core.annotation.MockInvoke
class BSvcMock {
@MockMethod(targetClass = String::class, targetMethod = "format")
@MockInvoke(targetClass = String::class, targetMethod = "format")
fun b_format(format: String, vararg args: Any?): String {
return "b_mock"
}

View File

@@ -1,10 +1,10 @@
package com.alibaba.demo.one2multi
import com.alibaba.testable.core.annotation.MockMethod
import com.alibaba.testable.core.annotation.MockInvoke
class CSvcMock {
@MockMethod(targetClass = String::class, targetMethod = "format")
@MockInvoke(targetClass = String::class, targetMethod = "format")
fun c_format(format: String, vararg args: Any?): String {
return "c_mock"
}

View File

@@ -1,7 +1,7 @@
package com.alibaba.demo.one2multi
import com.alibaba.testable.core.annotation.MockWith
import com.alibaba.testable.core.matcher.InvokeVerifier.verify
import com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
@@ -17,9 +17,9 @@ class OneToMultiSvcTest {
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)
verifyInvoked("a_format").withTimes(1)
verifyInvoked("b_format").withTimes(1)
verifyInvoked("c_format").withTimes(1)
}
}

View File

@@ -14,8 +14,8 @@ repositories {
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.8')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.6.8')
testImplementation('com.alibaba.testable:testable-all:0.7.9')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.7.9')
}
tasks.withType(JavaCompile) {

View File

@@ -12,7 +12,7 @@
<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.8</testable.version>
<testable.version>0.7.9</testable.version>
</properties>
<dependencyManagement>
@@ -70,7 +70,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<version>3.0.0-M7</version>
<configuration>
<argLine>-javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
<useFile>false</useFile>

View File

@@ -1,12 +1,12 @@
package com.github.pbetkier.spockdemo
import com.alibaba.testable.core.annotation.MockConstructor
import com.alibaba.testable.core.annotation.MockMethod
import com.alibaba.testable.core.annotation.MockNew
import com.alibaba.testable.core.annotation.MockInvoke
import com.github.pbetkier.spockdemo.model.SpockBox
import spock.lang.Shared
import spock.lang.Specification
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
import static com.alibaba.testable.core.matcher.InvocationVerifier.verifyInvoked;
class DemoSpockTest extends Specification {
@@ -14,14 +14,14 @@ class DemoSpockTest extends Specification {
def demoSpock = new DemoSpock()
static class Mock {
@MockConstructor
@MockNew
SpockBox createBox() {
SpockBox box = new SpockBox()
box.put("mock zero")
return box
}
@MockMethod(targetMethod = "put")
@MockInvoke(targetMethod = "put")
void putBox(SpockBox self, String data) {
self.put("mock " + data)
}
@@ -37,8 +37,8 @@ class DemoSpockTest extends Specification {
box.pop() == "mock 2"
box.pop() == "mock 1"
box.pop() == "mock zero"
verify("createBox").withTimes(1)
verify("putBox").withInOrder("1").withInOrder("2").withInOrder("3")
verifyInvoked("createBox").withTimes(1)
verifyInvoked("putBox").withInOrder("1").withInOrder("2").withInOrder("3")
}
}

View File

@@ -10,4 +10,4 @@ However, when the current mainstream mock framework implements the mock function
Therefore, we developed `TestableMock`, **a maverick and lightweight mock tool**.
![mock](https://testable-code.oss-cn-beijing.aliyuncs.com/en-us/mock-simpson.png)
![mock-simpson-en-us.png](https://img.alicdn.com/imgextra/i2/O1CN01CdAfqR1tP2iqFC14g_!!6000000005893-2-tps-500-761.png)

View File

@@ -0,0 +1,131 @@
Collection Tools
---
During test data preparation, various collection types are often used. However, collection data initialization in Java needs series of redundant call to the `add()` or `put()` method, making the code very tedious.
Inspired by the collection operations of Kotlin language, `TestableMock` draws on a set of concise collection construction method, which makes the creation of Java collection objects elegant.
The utility class `CollectionTool` provides following global static methods:
## arrayOf
Quickly construct an array of any type, equivalent to Kotlin's `arrayOf()` method.
Usage example:
```java
import static com.alibaba.testable.core.tool.CollectionTool.arrayOf;
// Construct a string array
String[] manyWords = arrayOf("this", "is", "an", "example");
// Construct a double array
Double[] manyValues = arrayOf(1.0D, 2.0D, 3.0D);
```
## listOf
Quickly construct an list of any type, equivalent to Kotlin's `mutableListOf()` method.
Usage example:
```java
import static com.alibaba.testable.core.tool.CollectionTool.listOf;
// Construct a string list
List<String> manyWords = listOf("this", "is", "an", "example");
// Construct a double list
List<Double> manyValues = listOf(1.0D, 2.0D, 3.0D);
```
## setOf
Quickly construct an set of any type, equivalent to Kotlin's `mutableSetOf()` method.
Usage example:
```java
import static com.alibaba.testable.core.tool.CollectionTool.setOf;
// Construct a string set
Set<String> manyWords = setOf("this", "is", "an", "example");
// Construct a double set
Set<Double> manyValues = setOf(1.0D, 2.0D, 3.0D);
```
## mapOf
Quickly construct an map of any type, equivalent to Kotlin's `mutableMapOf()` method.
Usage example:
```java
import static com.alibaba.testable.core.tool.CollectionTool.mapOf;
import static com.alibaba.testable.core.tool.CollectionTool.entryOf;
// Construct a string to string map
Map<String, String> manyWords = mapOf(
entryOf("language", "java"),
entryOf("type", "unittest"),
entryOf("library", "testable")
);
// Construct a integer to double map
Map<Integer, Double> manyValues = mapOf(
entryOf(1, 10.12D),
entryOf(2, 20.24D),
entryOf(3, 30.36D)
);
```
## orderedMapOf
Quickly construct ordered map (LinkedListMap) of any type. Compared with the HashMap map created by `mapOf()`, when traversing its keys or values in a loop, the order will always be the same as entries passed to `orderedMapOf()` method during construction.
Usage example:
```java
import static com.alibaba.testable.core.tool.CollectionTool.orderedMapOf;
import static com.alibaba.testable.core.tool.CollectionTool.entryOf;
// Construct a string to string ordered map
Map<String, String> manyWords = orderedMapOf(
entryOf("language", "java"),
entryOf("type", "unittest"),
entryOf("library", "testable")
);
// Construct a integer to double ordered map
Map<Integer, Double> manyValues = orderedMapOf(
entryOf(1, 10.12D),
entryOf(2, 20.24D),
entryOf(3, 30.36D)
);
```
## entryOf
Used with `mapOf()` and `orderedMapOf()` methods to construct a map.
## slice
Used to extract a part of data from any type of array to a new array.
Usage example:
```java
import static com.alibaba.testable.core.tool.CollectionTool.slice;
import static com.alibaba.testable.core.tool.CollectionTool.arrayOf;
// Extract the elements of the specified string array from subscript 1 to 3
// the returned new array content are ["is", "a", "simple"]
String[] fullWords = arrayOf("this", "is", "a", "simple", "example");
String[] partOfWords = slice(fullWords, 1, 3);
// If only given the index of starting position, all elements after this position are extracted
// the new array returned in the following example is [5, 8, 13, 21]
Integer[] fibonacci = arrayOf(1, 1, 2, 3, 5, 8, 13, 21);
Integer[] partOfFibonacci = slice(fibonacci, 4);
```

View File

@@ -19,7 +19,7 @@ Besides `TestableMock`, there are also several other community mock tools, such
`JMockit` is a mock tool whose functionality and convenience are between `Mockito` and `PowerMock`, and it makes up for their respective shortcomings. The project tried to launch a rewritten version of JMockit2 in 2017 but failed to complete, and is currently in an inactive maintenance state.
The functionality of `TestabledMock` is basically the same as that of `PowerMock`, and it is extremely easy to use. You can complete most tasks only by mastering the annotations of `@MockMethod`.
The functionality of `TestabledMock` is basically the same as that of `PowerMock`, and it is extremely easy to use. You can complete most tasks only by mastering the annotations of `@MockInvoke`.
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.

View File

@@ -25,9 +25,9 @@ In the code, there are often cases of using <u>interface variables or parent cla
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 `targetClass` value of `@MockInvoke` annotation (or the first parameter type of the mock method) should use the corresponding parent type / interface type.
See the use case of the `DemoInheritTest` test class in the Java and Kotlin examples.
See the use case of the `DemoReferenceTest` test class in the Java and Kotlin examples.
#### 5. How to mock generic methods (template methods)?
@@ -61,4 +61,10 @@ This framework will run Android unit tests on a standard JVM virtual machine, wh
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`.
![jar-manifest](https://testable-code.oss-cn-beijing.aliyuncs.com/jar-manifest.png)
![jar-manifest.png](https://img.alicdn.com/imgextra/i2/O1CN01hfC5YE1Kw0gBIlB2x_!!6000000001227-2-tps-752-171.png)
#### 10. Getting "no unique public constructor for [xxx]" error with `OmniConstructor`?
When the `omni.constructor.enhance.enable` configuration is enabled, `TestableMock` will dynamically add a constructor with a parameter of `java.lang.Void` when each class is loaded into memory to avoid the original type constructor exceptions that may be thrown. The above error occurs when some frameworks do not allow multiple constructors for some classes.
The solution is to add the package path where the error class is located to the `omni.constructor.enhance.pkgPrefix.excludes` configuration, which tell `TestableMock` not to dynamically add constructors to these classes. For details, please refer to the [Global Run Arguments](en-us/doc/javaagent-args.md) document.

View File

@@ -9,7 +9,7 @@ The **verifiers** and **matchers** are provided in `TestableMock` to achieve thi
@Test
public test_case() {
int res = insToTest.methodToTest();
verify("mockMethod").with(123, "abc");
verifyInvoked("mockMethod").with(123, "abc");
}
```

View File

@@ -35,9 +35,9 @@ Parent parent = OmniConstructor.newInstance(Parent.class);
parent.getChild().getGrandChild().getContent();
```
> **Note 1**: In the current version, the member fields of type interface or abstract class will still be initialized to `null`, this problem will be fixed in the future.
>
> **Note 2**: Based on the light-weight principle, in the default mode, `OmniConstructor` will only uses the original constructor of the class to create objects. For POJO and most model layer objects, this mode has been able to meet the needs.
> Interface and abstract class can also be instantiated by `OmniConstructor`, you can try it e.g. `OmniConstructor.newInstance(CharSequence.class)` ^_^
> **Notice**: Based on the light-weight principle, in the default mode, `OmniConstructor` will only uses the original constructor of the class to create objects. For POJO and most model layer objects, this mode has been able to meet the needs.
> But for more complex situations, such as when certain class have constructors throwing exceptions or contain other statements that hinder the normal execution of the construction, the object construction may fail.
> In those situations, you can use the [Testable global configuration](en-us/doc/javaagent-args.md) `omni.constructor.enhance.enable = true` to enable bytecode enhancement mode of `OmniConstructor`, in this mode, any Java class can be constructed.

View File

@@ -1,5 +1,55 @@
# Release Note
## 0.7.9
- fix many issues relate to interface instance creation of `OmniConstructor`
- add `@MockContainer` annotation to support multiply parents inheritance between mock classes
- add `targetClassName` parameter to `@MockInvoke` annotation, support mock method of private internal class
- move collection creation methods from `CollectionUtil` class to `CollectionTool` class
## 0.7.8
- support use `OmniConstructor` class to create instance of interface type or abstract class
- provide a `CollectionUtil` class which contains many utility collection creation methods
- fix a compatibility issue with jdk 1.6
## 0.7.7
- skip bytecode process for classes which not associated to any mock class, speed up test initialization
- only generate substitution method for mocked method references
- fix compatibility issues with jdk 17
## 0.7.6
- define package mapping rule according to order in config, to avoid a short path overwrite longer ones
- fix compatibility issue with jdk 9+
- skip classes generated by mockito during `OmniConstructor` bytecode enhancing
## 0.7.5
- fix an issue cause mocking method with array return type fails
## 0.7.4
- fix an `OmniConstructor.newInstance()` fail issue caused by `java.net.URL` class cannot be instrumented
## 0.7.3
- fix an issue cause mock failed with referring to method defined in interface
## 0.7.2
- fix an issue cause mock failed with static method reference
## 0.7.1
- fix an issue cause mock class unrecognized after package-mapping
## 0.7.0
- fix an improper exception issue when mock method with `associated` scope invoked
- rename type `InvokeVerifier`/`InvockeMatcher` to `InvocationVerifier`/`InvocationMatcher`
- rename method `verify` in `InvocationVerifier` type to `verifyInvoked`
- rename annotation `@MockMethod`/`@MockConstructor` to `@MockInvoke`/`@MockNew`
## 0.6.10
- support mock invocation via function reference
- support mock method defined in a base interface
## 0.6.9
- support mock invocation in lambda method
- fix a `NullPointerException` issue when `PrivateAccessor.invoke()` has `null` parameter
## 0.6.8
- support `@DumpTo` annotation to dump bytecode of any transformed class
- `PrivateAccessor.setStatic()` method now able to update static final members
@@ -58,6 +108,13 @@
- fix an exception caused by method parameter with ternary operator
- fix a bug cause log message lost when `@MockWith` annotation used
## 0.5.0
- split test class and mock class, let the mock class and method reusable
- support use mock when package path of the test class is different from the class under test
- support limit the mock effective scope to the cases of class under test it bound
- use `TransmittableThreadLocal` to store mock context
- add annotation `@MockDiagnose` to print debug logs
## 0.4.12
- support verbose diagnose log for better self-troubleshooting
- support disable private access target existence check

View File

@@ -7,6 +7,7 @@ Use TestableMock
- [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
- [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
- [Assist 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 create collection instances](en-us/doc/collection-tools.md): provide utility methods for collection creation, solve the problem of redundant codes during java test data preparation
## Use in Maven project
@@ -16,7 +17,7 @@ It is recommended to add a `property` field that identifies the TestableMock ver
```xml
<properties>
<testable.version>0.6.8</testable.version>
<testable.version>0.7.9</testable.version>
</properties>
```
@@ -63,8 +64,8 @@ Add dependence of `TestableMock` in `build.gradle` file:
```groovy
dependencies {
testImplementation('com.alibaba.testable:testable-all:0.6.8')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.6.8')
testImplementation('com.alibaba.testable:testable-all:0.7.9')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.7.9')
}
```

View File

@@ -70,7 +70,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).
After executing the void type method under test, use `InvokeVerifier.verify()` to verify whether the incoming print content meets expectations:
After executing the void type method under test, use `InvocationVerifier.verifyInvoked()` to verify whether the incoming print content meets expectations:
```java
class DemoTest {
@@ -78,7 +78,7 @@ class DemoTest {
public static class Mock {
// Intercept `System.out.println` invocation
@MockMethod
@MockInvoke
public void println(PrintStream ps, String msg) {
// Execute the original call
ps.println(msg);
@@ -90,7 +90,7 @@ class DemoTest {
Action action = new Action("click", ":download");
demo.recordAction();
// Verify mock method `println` is invoked, and passing parameters in line with expectations
verify("println").with(matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2} \\[click\\] :download"));
verifyInvoked("println").with(matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2} \\[click\\] :download"));
}
}
```

View File

@@ -1,6 +1,15 @@
Upgrade Guide
---
#### Upgrade to 0.7
In version `0.7` mock related annotations and methods are renamed, in order to avoid naming conflict and misunderstanding.
1. The `verify()` used for check mock method invocation status has been renamed to `verifyInvoked()`, as there are several static methods also named `verify()` in `com.sun` package.
2. The annotation `@MockMethod` and `@MockConstructor` used for mark mock replacement method has been renamed to `@MockInvoke` and `@MockNew`, which is more suitable for its real usage.
The usage of those annotations and method has no change for below and above version `0.7.0`, so during the upgrading only rename is required.
### 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.
@@ -39,7 +48,7 @@ public class DemoMockTest {
@Test
void should_mock_member_method() throws Exception {
assertEquals("hello_world", demoMock.outerFunc());
verify("innerFunc").with("world");
verifyInvoked("innerFunc").with("world");
}
}
```
@@ -59,7 +68,7 @@ public class DemoMockTest {
@Test
void should_mock_member_method() throws Exception {
assertEquals("hello_world", demoMock.outerFunc());
verify("innerFunc").with("world");
verifyInvoked("innerFunc").with("world");
}
}
```

View File

@@ -15,4 +15,4 @@ At the same time, because the built-in unit test executor of `Eclipse` completel
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.
![eclipse-junit-configuration](https://testable-code.oss-cn-beijing.aliyuncs.com/eclipse-junit-configuration.png)
![eclipse-junit-configuration.png](https://img.alicdn.com/imgextra/i3/O1CN01C7DwGs1dHgVRAhh3y_!!6000000003711-2-tps-1430-1004.png)

View File

@@ -0,0 +1,46 @@
Use IntelliJ Plugin
---
There are some typical operations in the process of using`TestableMock` library, such as define a mock class, copy signature of the method invoked and convert it to mock method definition. While the mock result does not match expectation, we have to debug the mock affection via [Self-Help Troubleshooting](zh-cn/doc/troubleshooting.md) guide during runtime. With the help of IDE plugin, part of the patterned operations can be automatically completed to further optimize the experience of `TestableMock`.
Currently `TestableMock` provided only [IntelliJ IDEA](https://www.jetbrains.com/idea/) plugin. (According to the [2020](https://www.jrebel.com/blog/2020-java-technology-report#IDE) and [2021](https://www.jrebel.com/blog/2021-java-technology-report) Java Technology Trends Report released by JRebel, IntelliJ IDEA has become the IDE of choice with the highest percentage of Java developers)
## Usage
Open preference dialog, search "Testable-Mock" in plugin market, reach `Testable-Mock Helper` plugin and click "install", then restart IDE.
![install](https://img.alicdn.com/imgextra/i2/O1CN013twjqN2803NVtvJEP_!!6000000007869-0-tps-2342-566.jpg)
#### 1. **Generate Mock Class**
After plugin installed, right click in any java class, choose "Generate Testable TestClass"
![mock-class](https://img.alicdn.com/imgextra/i1/O1CN01gNsunh21jfVufv6EU_!!6000000007021-0-tps-546-291.jpg)
A test class containing a mock container class will be automatically generated in the same package path location as the current class in the `test` directory of the project (named after `CurrentClass + Test`)
#### 2. **Create Mock Method**
Select any method invocation, right click and choose "Copy Testable Mock-Method" or "Copy Mock-Method To TestClass"
![mock-method](https://img.alicdn.com/imgextra/i4/O1CN01K38Zsh1NrNYLlrUZ9_!!6000000001623-0-tps-642-264.jpg)
The former will convert the method signature of selected invocation into a mock method definition and store it on the clipboard; the latter will directly insert the converted mock method into the corresponding mock class.
## Build from source code
Beside install plugin from market, you can also build it from source.
Make sure you have jdk 11 installed, execute below command:
```bash
git clone https://github.com/zcbbpo/testable-idea
cd testable-idea
./gradlew clean build
```
After build, the packaged zip file is lay in `build/distributions/` folder, you can install it by "Install Plugin From Disk" menu.
![from-local](https://img.alicdn.com/imgextra/i3/O1CN01YMHefk26MNS4pH4ZI_!!6000000007647-0-tps-2342-516.jpg)
Finally, special thanks to the contribution of `TestableMock` IntelliJ IDEA plugin made by @[zcbbpo](https://github.com/zcbbpo).

View File

@@ -29,13 +29,13 @@ This is because the IDE usually only runs the `maven-surefire-plugin` plugin whe
This problem can be bypassed by configuring the test parameters of the IDE additionally. Take IntelliJ as an example, open the "Edit Configuration..." option of the run menu, as shown in the position ①
![modify-run-configuration](https://testable-code.oss-cn-beijing.aliyuncs.com/modify-run-configuration.png)
![modify-run-configuration.png](https://img.alicdn.com/imgextra/i3/O1CN01HLlNyZ1gezVe4AOiE_!!6000000004168-2-tps-1036-184.png)
Add JavaAgent startup parameters at the end of the "virtual machine parameters" attribute value: `-javaagent:${HOME}/.m2/repository/com/alibaba/testable/testable-agent/xyz/testable-agent-xyzjar`, as shown in the figure position ②
> PS: Please replace `x.y.z` in the path with the actual version number
![add-testable-javaagent](https://testable-code.oss-cn-beijing.aliyuncs.com/add-testable-javaagent.png)
![add-testable-javaagent.png](https://img.alicdn.com/imgextra/i4/O1CN01pdxC8S1R2JpXX8aOJ_!!6000000002053-2-tps-2446-486.png)
Finally, click to run the unit test, as shown in the position ③

View File

@@ -6,8 +6,8 @@ In unit testing, the main role of the mock method is to replace those methods wi
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
> - Mock non-constructive method, copy the original method definition to the mock class, add a `@MockInvoke` 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 `@MockNew` annotation
The detail mock method definition convention is as follows.
@@ -27,7 +27,7 @@ public class DemoTest {
### 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.
Define an ordinary method annotated with `@MockInvoke` 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 `@MockInvoke` 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.
@@ -36,33 +36,33 @@ For example, there is a call to `"anything".substring(1, 2)` in the class under
```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)
@MockInvoke(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.
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 `@MockInvoke` 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")
@MockInvoke(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.
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 `@MockInvoke` 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:
The `TestableMock` convention is that when the `targetClass` parameter of the `@MockInvoke` 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
@MockInvoke
private String substring(String self, int i, int j) {
// Call the original method is also allowed
return self.substring(i, j);
@@ -81,7 +81,7 @@ For example, there is a private method with the signature `String innerFunc(Stri
```java
// The type to test is `DemoMock`
@MockMethod(targetClass = DemoMock.class)
@MockInvoke(targetClass = DemoMock.class)
private String innerFunc(String text) {
return "mock_" + text;
}
@@ -98,7 +98,7 @@ 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)
@MockInvoke(targetClass = BlackBox.class)
private BlackBox secretBox() {
return new BlackBox("not_secret_box");
}
@@ -108,7 +108,7 @@ For complete code examples, see the `should_mock_static_method()` test case in t
### 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.
Define an ordinary method annotated with `@MockNew` 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.
@@ -117,13 +117,13 @@ For example, if there is a call to `new BlackBox("something")` in the class unde
```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
@MockNew
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
> You can still use the `@MockInvoke` 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 `@MockNew` annotation
For complete code examples, see the `should_mock_new_object()` test case in the `java-demo` and `kotlin-demo` sample projects.
@@ -146,7 +146,7 @@ public void testDemo() {
Take out the injected parameters in the mock method and return different results according to the situation:
```java
@MockMethod
@MockInvoke
private Data mockDemo() {
switch((String)MOCK_CONTEXT.get("case")) {
case "data-ready":
@@ -159,13 +159,13 @@ private Data mockDemo() {
}
```
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.
For complete code examples, see the `should_get_source_method_name()` and `should_set_mock_context()` 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.
In test cases, you can use the `InvocationVerifier.verifyInvoked()` 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.
For details, please refer to the [Check Mock Call](en-us/doc/invoke-matcher.md) document.
### 4. Special instructions

View File

@@ -4,6 +4,7 @@
- [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)
- [Collection Tools](en-us/doc/collection-tools.md)
- Usage Guide
- [Verify Mock Invocation](en-us/doc/invoke-matcher.md)
@@ -11,6 +12,7 @@
- [Reusing Mock Methods](en-us/doc/mock-method-reusing.md)
- [Use MockWith Annotation](en-us/doc/use-mock-with.md)
- [Use Package Mapping](en-us/doc/use-package-mapping.md)
- [Use IntelliJ Plugin](en-us/doc/use-intellij-plugin.md)
- [Frequently Asked Questions](en-us/doc/frequently-asked-questions.md)
- [Mock Invocation In Thread Pool](zh-cn/doc/with-thread-pool.md)
- [Use TestableMock In IDE](en-us/doc/use-in-ide.md)

View File

@@ -9,4 +9,4 @@ TestableMock简介
于是,我们开发了`TestableMock`**一款特立独行的轻量Mock工具**。
![mock](https://testable-code.oss-cn-beijing.aliyuncs.com/mock-simpson.png)
![mock-simpson-zh-cn.png](https://img.alicdn.com/imgextra/i2/O1CN01uPzi441cxzTZzhUWT_!!6000000003668-2-tps-500-761.png)

View File

@@ -14,19 +14,22 @@
| srcClass | Class | 否 | N/A | 当测试类命名不符合约定时,指定实际被测类 |
| verifyTargetOnCompile | boolean | 否 | true | 是否启用私有目标的编译期存在性校验 |
#### @MockMethod
#### @MockInvoke
将当前方法标识为待匹配的Mock成员方法。
- 作用于Mock容器类中的方法
| 参数 | 类型 | 是否必须 | 默认值 | 作用 |
| --- | --- | --- | ---- | --- |
| targetClass | Class | 否 | N/A | 指定Mock目标的调用者类型 |
| targetMethod | String | 否 | N/A | 指定Mock目标的方法名 |
| scope | MockScope | 否 | MockScope.GLOBAL | 指定Mock的生效范围 |
| 参数 | 类型 | 是否必须 | 默认值 | 作用 |
| --- | --- | --- | ---- | --- |
| targetClass | Class | 否 | N/A | 指定Mock目标的调用者类型 |
| targetClassName | String | 否 | N/A | `targetClass`相同,主要用于无法直接引用的私有内部类 |
| targetMethod | String | 否 | N/A | 指定Mock目标的方法名 |
| scope | MockScope | 否 | MockScope.GLOBAL | 指定Mock的生效范围 |
#### @MockConstructor
> 说明:`targetClass`和`targetClassName`参数不能同时使用
#### @MockNew
将当前方法标识为待匹配的Mock构造方法。
@@ -36,6 +39,15 @@
| --- | --- | --- | ---- | --- |
| scope | MockScope | 否 | MockScope.GLOBAL | 指定Mock的生效范围 |
#### @MockContainer
标识指定类为Mock容器类并从任意个其他Mock类里继承所有Mock方法。
- 作用于Mock容器类
| 参数 | 类型 | 是否必须 | 默认值 | 作用 |
| ---- | --- | --- | --- | --- |
| inherits | Class[] | 否 | N/A | 指定要继承的Mock类 |
#### @MockWith

View File

@@ -0,0 +1,129 @@
常用集合构建工具
---
在编写测试数据的时候经常会用到各种集合类型的对象然而Java原生的集合类型需要反复调用`add()``put()`方法来添加数据,使代码显得十分冗长。
`TestableMock`借鉴了Kotlin语言简洁的集合构造方法提供了一个实用的集合构造工具类`CollectionTool`让Java集合对象的创建从此变得优雅。
这个工具类提供了以下全局静态方法:
## arrayOf
快速构造任意类型的数组等效于Kotlin的`arrayOf()`方法。
用法示例:
```java
import static com.alibaba.testable.core.tool.CollectionTool.arrayOf;
// 构造一个字符串数组
String[] manyWords = arrayOf("this", "is", "an", "example");
// 构造一个浮点值数组
Double[] manyValues = arrayOf(1.0D, 2.0D, 3.0D);
```
## listOf
快速构造任意类型的列表等效于Kotlin的`mutableListOf()`方法。
用法示例:
```java
import static com.alibaba.testable.core.tool.CollectionTool.listOf;
// 构造一个字符串列表
List<String> manyWords = listOf("this", "is", "an", "example");
// 构造一个浮点值列表
List<Double> manyValues = listOf(1.0D, 2.0D, 3.0D);
```
## setOf
快速构造任意类型的集合等效于Kotlin的`mutableSetOf()`方法。
用法示例:
```java
import static com.alibaba.testable.core.tool.CollectionTool.setOf;
// 构造一个字符串集合
Set<String> manyWords = setOf("this", "is", "an", "example");
// 构造一个浮点值集合
Set<Double> manyValues = setOf(1.0D, 2.0D, 3.0D);
```
## mapOf
快速构造任意类型的无序映射表等效于Kotlin的`mutableMapOf()`方法。
用法示例:
```java
import static com.alibaba.testable.core.tool.CollectionTool.mapOf;
import static com.alibaba.testable.core.tool.CollectionTool.entryOf;
// 构造一个字符串到字符串的映射
Map<String, String> manyWords = mapOf(
entryOf("language", "java"),
entryOf("type", "unittest"),
entryOf("library", "testable")
);
// 构造一个整型数值到浮点数值的映射
Map<Integer, Double> manyValues = mapOf(
entryOf(1, 10.12D),
entryOf(2, 20.24D),
entryOf(3, 30.36D)
);
```
## orderedMapOf
快速构造任意类型的有序映射表LinkedListMap相较于`mapOf()`创建的HashMap对象当在循环中遍历它的键或值列表时其访问顺序始终会与构造时传给`orderedMapOf()`方法的数据顺序一致。
用法示例:
```java
import static com.alibaba.testable.core.tool.CollectionTool.orderedMapOf;
import static com.alibaba.testable.core.tool.CollectionTool.entryOf;
// 构造一个字符串到字符串的有序映射
Map<String, String> manyWords = orderedMapOf(
entryOf("language", "java"),
entryOf("type", "unittest"),
entryOf("library", "testable")
);
// 构造一个整型数值到浮点数值的有序映射
Map<Integer, Double> manyValues = orderedMapOf(
entryOf(1, 10.12D),
entryOf(2, 20.24D),
entryOf(3, 30.36D)
);
```
## entryOf
配合`mapOf()``orderedMapOf()`方法使用,用于构造映射表。
## slice
用于提取任意类型数组中间的一部分数据,组成新的数组。
用法示例:
```java
import static com.alibaba.testable.core.tool.CollectionTool.slice;
import static com.alibaba.testable.core.tool.CollectionTool.arrayOf;
// 提取指定字符串数组从下标1到3之间的元素得到的新数组内容为["is", "a", "simple"]
String[] fullWords = arrayOf("this", "is", "a", "simple", "example");
String[] partOfWords = slice(fullWords, 1, 3);
// 只传入起始位置下标,提取该位置之后的所有元素,下例返回的新数组内容为[5, 8, 13, 21]
Integer[] fibonacci = arrayOf(1, 1, 2, 3, 5, 8, 13, 21);
Integer[] partOfFibonacci = slice(fibonacci, 4);
```

View File

@@ -9,7 +9,7 @@
| Spock | 动态代理 | 类 | 不能Mock私有/静态和构造方法 | 较复杂 | 一般 |
| PowerMock | 自定义类加载器 | 类 | **任何方法皆可** | 较复杂 | **较好** |
| JMockit | 运行时字节码修改 | 类 | 不能Mock构造方法(new操作符) | 较复杂 | 一般 |
| TestableMock | 运行时字节码修改 | 方法 | **任何方法皆可** | **容易** | 一般 |
| TestableMock | 运行时字节码修改 | 方法 | **任何方法皆可** | **容易** | **较好** |
`Mockito`是Java最老牌的Mock工具稳定性和易用性较好IntelliJ和Eclipse都有专用插件支持。相对不足之处在于Mock功能稍弱在必要情况下需与其他Mock工具配合使用。
@@ -19,8 +19,12 @@
`JMockit`是一款功能性与易用性均居于`Mockito``PowerMock`之间的Mock工具较好的弥补了两者各自的不足。该项目在2017年尝试推出JMockit2重写版本但未能完成目前处于不活跃的维护状态。
相比之下,`TestabledMock`的功能与`PowerMock`基本平齐,且极易上手,只需掌握`@MockMethod`注解就可以完成绝大多数任务
相比之下,`TestabledMock`的功能与`PowerMock`基本平齐,且极易上手,只需掌握一个`@MockInvoke`注解就可以完成绝大多数Mock操作
当前`TestableMock`的主要不足在于编写Mock方法时IDE无法即时提示方法参数是否正确匹配。若发现匹配效果不符合预期需要通过[自助问题排查](zh-cn/doc/troubleshooting.md)文档提供的方法在运行期进行校验。这个功能理论上能够通过扩展主流IDE插件来补充但目前暂无相关开发计划参见[Issue-104](https://github.com/alibaba/testable-mock/issues/104)。
## TestableMock的不足
此外,由于`TestableMock`独辟蹊径的采用“每个业务类拥有一个专属Mock容器类”的思维方式将Mock方法定义与单元测试用例解耦一方面使得Mock方法具有默认可复用性单元测试用例也因此变得更干净纯粹另一方面也导致Mock方法定义变得零散生命周期管理起来相对困难对现有开发者的Mock编写习惯会带来一定改变。
为了便于开发者进行Mock工具的合理选型我们认为客观的强调`TestabledMock`存在的不足,与阐述它的优点同样重要。
任何事物都有两面性。由于`TestableMock`独辟蹊径采用“每个业务类拥有一个专属Mock容器类”的思维方式将Mock方法定义与单元测试用例解耦一方面使得Mock方法具有默认可复用性单元测试用例也因此变得更干净纯粹另一方面也导致Mock方法定义变得零散生命周期管理起来相对困难对现有开发者的Mock编写习惯会带来一定改变。
此外,`TestableMock`最初的设计主要面向标准单元测试场景(每个类有自己的测试用例,测试类与被测类基本逐一对应)。为了最大限度的提升易用性,把需要用到的注解、操作步骤都减少到最少,`TestableMock`采用了许多“约定优于配置”的设计。譬如被测量、测试类与Mock容器类在同一包路径且分别按约定命名等。对于单元测试较规范的项目而言`TestableMock`将极大降低Mock引入的成本对于规范不足的项目依然可以结合`@MockWith`、包路径映射等功能完成相同的Mock效果但应当尤其注意这些“花哨”的功能对代码可读性和可维护性带来的负面影响。

View File

@@ -29,9 +29,9 @@
这时候遵循一个原则Mock方法的首个参数类型**始终与发起调用的变量类型一致**。
因此不论实际被调用方法来自父类还是子类也不论子类是否覆写该方法。若发起调用的变量为父类型或接口类型则Mock方法的首个参数类型都应该使用相应的父类(或接口)类型。
因此,不论实际被调用方法来自父类还是子类,也不论子类是否覆写该方法。若发起调用的变量为父类型(或接口类型),则`@MockInvoke`注解的`targetClass`参数值(或Mock方法的首个参数类型都应该使用相应的父类/接口类的类型。
参见Java和Kotlin示例中`DemoInheritTest`测试类的用例。
参见Java和Kotlin示例中`DemoReferenceTest`测试类的用例。
#### 5. 如何Mock对于泛型方法(模板方法)
@@ -63,4 +63,10 @@ Kotlin语言中的`String`类型实际上是`kotlin.String`,而非`java.lang.S
这个问题是由于系统ClassPath包含太多路径所致与是否使用`TestableMock`无关。但需要注意的是IntelliJ提供了两种辅助解决机制`JAR manifest``classpath file`,若测试中使用了`TestableMock`,请选择`JAR manifest`
![jar-manifest](https://testable-code.oss-cn-beijing.aliyuncs.com/jar-manifest.png)
![jar-manifest.png](https://img.alicdn.com/imgextra/i2/O1CN01hfC5YE1Kw0gBIlB2x_!!6000000001227-2-tps-752-171.png)
#### 10. 使用`OmniConstructor`遇到"no unique public constructor for [xxx]"错误?
当启用`omni.constructor.enhance.enable`配置时,`TestableMock`会在每个类加载进内存时动态添加一个参数为`java.lang.Void`的构造方法,用于避开原类型构造方法里可能抛出的异常。若遇到某些框架不允许类型存在多个构造方法的时候,就会出现上述错误。
解决办法是将报错类所在的包路径添加到`omni.constructor.enhance.pkgPrefix.excludes`配置中,告知`TestableMock`不要对这些类动态添加构造方法即可。配置方法详见[全局运行参数](zh-cn/doc/javaagent-args.md)文档。

View File

@@ -9,7 +9,7 @@
@Test
public test_case() {
int res = insToTest.methodToTest();
verify("mockMethod").with(123, "abc");
verifyInvoked("mockMethod").with(123, "abc");
}
```

View File

@@ -59,4 +59,4 @@
若项目测试中既包含真实的单元测试又包含了使用单元测试框架编写的集成测试时。为了让集成测试的执行过程不受Mock影响可能需要使用`mock.scope.default`将默认的Mock方法范围限制为仅对所属类型的单元测试用例生效。
若需Mock的调用发生在线程池中且遇到`verify()`结果或`MOCK_CONTEXT`内容不正确的时候,则需考虑开启`thread.pool.enhance.enable`配置,详见[Mock线程池内的调用](zh-cn/doc/with-thread-pool.md)。
若需Mock的调用发生在线程池中且遇到`verifyInvoked()`结果或`MOCK_CONTEXT`内容不正确的时候,则需考虑开启`thread.pool.enhance.enable`配置,详见[Mock线程池内的调用](zh-cn/doc/with-thread-pool.md)。

View File

@@ -51,7 +51,7 @@ public class BbbServiceTest {
}
public class BasicMock {
@MockMethod(targetClass = UserDao.class)
@MockInvoke(targetClass = UserDao.class)
protected String getById(int id) {
...
}

View File

@@ -35,9 +35,9 @@ Parent parent = OmniConstructor.newInstance(Parent.class);
parent.getChild().getGrandChild().getContent();
```
> **注意 1** :在当前版本中,类型为接口或抽象类的成员字段依然会被初始化为`null`,此问题将在后续版本修复
>
> **注意 2** :基于轻量优先原则,默认模式下,`OmniConstructor`仅利用类型原有的构造方法来创建对象对于绝大多数POJO和`Model`层对象而言这种模式已经能够满足需要。
> 接口类型和抽象类型也可以直接通过`OmniConstructor`实例化哦,不信试试看`OmniConstructor.newInstance(CharSequence.class)` ^_^
> **注意**:基于轻量优先原则,默认模式下,`OmniConstructor`仅利用类型原有的构造方法来创建对象对于绝大多数POJO和`Model`层对象而言这种模式已经能够满足需要。
> 但对于更复杂的情形,譬如遇到某些类型的构造方法会抛出异常或包含其他妨碍构造正常执行的语句时,对象构造可能会失败。
> 此时可通过[Testable全局配置](zh-cn/doc/javaagent-args.md)`omni.constructor.enhance.enable = true`启用`OmniConstructor`的字节码增强模式在该模式下任何Java类型皆可构造。

View File

@@ -17,6 +17,9 @@
- `PrivateAccessor.invokeStatic(任意类型, "私有静态方法名", 调用参数...)` ➜ 调用任意类的**静态**私有方法
- `PrivateAccessor.construct(任意类型, 构造方法参数...)` ➜ 调用任意类的私有构造方法
> 特别说明:默认情况下,`setStatic()`方法不支持修改`static final`修饰的成员变量。在Java中此类变量通常代表业务意义上的恒定常量值不应当在单元测试中更改。
> 在特殊场景下,如确实需要修改`static final`成员,请开启配置项`private.access.enhance.enable = true`,详见[全局运行参数](zh-cn/doc/javaagent-args.md)文档。
详见`java-demo``kotlin-demo`示例项目`DemoPrivateAccessorTest`测试类中的用例。
### 1.2 防代码重构机制

View File

@@ -1,5 +1,55 @@
# Release Note
## 0.7.9
- 修复多处`OmniConstructor`创建接口类型对象相关的问题
- 新增`@MockContainer`注解支持Mock容器类之间的多重继承
- `@MockInvoke`注解新增`targetClassName`参数支持Mock私有内部类的方法
-`CollectionUtil`中的集合构造方法独立为`CollectionTool`工具类并发布
## 0.7.8
- 支持使用`OmniConstructor`工具类自动创建接口和抽象类的实例
- 提供`CollectionUtil`工具类,包含在测试中常用的集合创建方法
- 修复一处JDK 1.6的兼容问题
## 0.7.7
- 未关联Mock类的类型自动跳过字节码处理提升大型项目测试启动速度
- 仅对被Mock的方法引用生成替代方法 (issue-280)
- 修复JDK 17的兼容性问题 (issue-283)
## 0.7.6
- 依据定义顺序匹配包映射规则防止短路径覆盖长路径issue-275
- 修复JDK 9+的兼容性问题issue-270 / pr-274
-`OmniConstructor`字节码增强时忽略Mockito生成的类型issue-197
## 0.7.5
- 修复Mock数组类型返回值方法报错的问题issue-267
## 0.7.4
- 修复`java.net.URL`类无法被instrument导致使用`OmniConstructor`构造报错的问题issue-261
## 0.7.3
- 修复接口方法引用导致Mock报错的问题issue-254
## 0.7.2
- 修复静态方法引用导致Mock报错的问题issue-250
## 0.7.1
- 修复包路径映射时后识别不到Mock类的问题issue-248
## 0.7.0
- 修复当`scope``associated`的Mock方法被`null`对象调用且上下文与测试用例未关联时抛错不合理的问题issue-163
- 类型`InvokeVerifier``InvockeMatcher`更名为`InvocationVerifier``InvocationMatcher`
- 类型`InvocationVerifier`中的`verify`方法与`com.sun`包中的方法重名,更名为`verifyInvoked`
- 注解`@MockMethod``@MockConstructor`更名为`@MockInvoke``@MockNew`
## 0.6.10
- 支持Mock通过方法引用执行的调用issue-233 / pr-234
- 支持Mock基类接口中的方法pr-231
## 0.6.9
- 支持Mock匿名方法体内部的方法调用issue-36 / pr-208
- 修复`PrivateAccessor.invoke()`调用参数为`null`时的空指针异常issue-226
## 0.6.8
- 支持使用`@DumpTo`注解导出任意类处理后的字节码
- 支持使用`PrivateAccessor.setStatic()`方法修改静态常量成员

View File

@@ -1,14 +1,14 @@
Mock的生效范围
---
`@MockMethod``@MockConstructor`注解上都有一个`scope`参数,其可选值有两种
`@MockInvoke``@MockNew`注解上都有一个`scope`参数,其可选值有两种
- `MockScope.GLOBAL`该Mock方法将全局生效
- `MockScope.ASSOCIATED`该Mock方法仅对Mock容器关联测试类中的测试用例生效
对于常规项目而言单元测试里需要被Mock的调用都是由于其中包含了不需要或不便于测试的逻辑譬如“依赖外部系统”、“包含随机结果”、“执行非常耗时”等等这类调用在整个单元测试的生命周期里都应该被Mock方法置换不论调用的发起者是谁。因此`TestableMock`默认所有Mock方法都是全局生效的`scope`默认值为`MockScope.GLOBAL`
> 举例来说,`CookerService`和`SellerService`是两个需要被测试的类,假设`CookerService`的代码里的`hireXxx()`和`cookXxx()`方法都需要依赖外部系统。因此在进行单元测试时,开发者在`CookerService`关联的Mock容器里使用`@MockMethod`注解定义了这些调用的替代方法。
> 举例来说,`CookerService`和`SellerService`是两个需要被测试的类,假设`CookerService`的代码里的`hireXxx()`和`cookXxx()`方法都需要依赖外部系统。因此在进行单元测试时,开发者在`CookerService`关联的Mock容器里使用`@MockInvoke`注解定义了这些调用的替代方法。
>
> 此时若该Mock方法的`scope`值为`MockScope.GLOBAL`,则不论是在`SellerServiceTest`测试类还是在`CookerServiceTest`测试类的测试用例只要直接或间接的执行到这行调用都会被置换为调用Mock方法。若该Mock方法的`scope`值为`MockScope.ASSOCIATED`则Mock只对`CookerServiceTest`类中的测试用例生效,而`SellerServiceTest`类中的测试用例在运行过程中执行到了`CookerService`类的相关代码,将会执行原本的调用。
>
@@ -24,7 +24,7 @@ Mock的生效范围
> ```
> 若默认的`scope`参数不是`MockScope.GLOBAL`则相应Mock方法应当显式的声明`scope`值,例如:
> ```java
> @MockMethod(targetClass = System.class, scope = MockScope.GLOBAL)
> @MockInvoke(targetClass = System.class, scope = MockScope.GLOBAL)
> private void loadLibrary(String libname) {
> System.err.println("loadLibrary " + libname);
> }

View File

@@ -7,6 +7,7 @@
- [访问被测类私有成员](zh-cn/doc/private-accessor.md):使单元测试能直接调用和访问被测类的私有成员,解决私有成员初始化和私有方法测试的问题
- [快速构造参数对象](zh-cn/doc/omni-constructor.md):生成任意复杂嵌套的对象实例,并简化其内部成员赋值方式,解决被测方法参数初始化代码冗长的问题
- [辅助测试void方法](zh-cn/doc/test-void-method.md)利用Mock校验器对方法的内部逻辑进行检查解决无返回值方法难以实施单元测试的问题
- [快速构造集合对象](zh-cn/doc/collection-tools.md)提供简洁易用的集合构造和常用操作方法解决准备测试数据时Java集合初始化代码冗长的问题
## 在Maven项目中使用
@@ -16,7 +17,7 @@
```xml
<properties>
<testable.version>0.6.8</testable.version>
<testable.version>0.7.9</testable.version>
</properties>
```
@@ -63,8 +64,8 @@
```groovy
dependencies {
testImplementation('com.alibaba.testable:testable-all:0.6.8')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.6.8')
testImplementation('com.alibaba.testable:testable-all:0.7.9')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.7.9')
}
```
@@ -110,4 +111,6 @@ test {
> sourceCompatibility = '6' // 或7/8/9/...
> ```
>
> **注意**:由于`Spock`采用基于字符串解析的特殊执行方式,`TestableTool`工具类在`Spock`框架中无法使用。
>
> 完整代码可参考`demo/spock-demo`示例项目。

View File

@@ -71,7 +71,7 @@ class Demo {
若要测试此方法,可以利用`TestableMock`快速Mock掉`System.out.println`方法。在Mock方法体里可以继续执行原调用相当于并不影响本来方法功能仅用于做调用记录也可以直接留空相当于去除了原方法的副作用
在执行完被测的void类型方法以后`InvokeVerifier.verify()`校验传入的打印内容是否符合预期:
在执行完被测的void类型方法以后`InvocationVerifier.verifyInvoked()`校验传入的打印内容是否符合预期:
```java
class DemoTest {
@@ -79,7 +79,7 @@ class DemoTest {
public static class Mock {
// 拦截System.out.println调用
@MockMethod
@MockInvoke
public void println(PrintStream ps, String msg) {
// 执行原调用
ps.println(msg);
@@ -91,7 +91,7 @@ class DemoTest {
Action action = new Action("click", ":download");
demo.recordAction();
// 验证Mock方法println被调用且传入参数格式符合预期
verify("println").with(matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2} \\[click\\] :download"));
verifyInvoked("println").with(matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2} \\[click\\] :download"));
}
}
```

View File

@@ -1,6 +1,15 @@
版本升级说明
---
#### 升级到0.7.x版本
`0.7`版本重命名了Mock相关的注解和方法以避免命名冲突和由于名字带来的理解歧义包括两部分变化。
1. 用于验证Mock方法调用的`verify()`方法与包括`com.sun`包在内的多个JVM内置静态方法重名在IDE自动添加`import`时会有误选的情况,该方法在新版本中更名为`verifyInvoked()`,同时所属的类型更名为`InvocationVerifier`
2. 注解`@MockMethod``@MockConstructor`的实际作用是对方法调用和New操作进行Mock而其名称容易使人误解为是对方法和构造器本身进行了Mock在新版本中这两个注解分别已被分别更名为`@MockInvoke``@MockNew`
这些注解和方法的使用与之前版本并无差异,从`0.6.x`版本升级到`0.7.0`及以上版本时,请全局替换相关名称即可。
### 升级到0.6.x版本
`0.6`版本中,`TestableMock`提供了[快速构造复杂参数对象](zh-cn/doc/omni-constructor.md)的能力,同时包含一处与`0.5`版本不兼容的修改,`PrivateAccessor`类型的包路径从`com.alibaba.testable.core.accessor`移到了`com.alibaba.testable.core.tool`
@@ -39,7 +48,7 @@ public class DemoMockTest {
@Test
void should_mock_member_method() throws Exception {
assertEquals("hello_world", demoMock.outerFunc());
verify("innerFunc").with("world");
verifyInvoked("innerFunc").with("world");
}
}
```
@@ -59,7 +68,7 @@ public class DemoMockTest {
@Test
void should_mock_member_method() throws Exception {
assertEquals("hello_world", demoMock.outerFunc());
verify("innerFunc").with("world");
verifyInvoked("innerFunc").with("world");
}
}
```

View File

@@ -15,4 +15,4 @@ IntelliJ IDE对`TestableMock`所用到的`JSR-269`注释处理器以及`maven-su
以使用`JUnit`为例方法为从IDE工具栏的运行按钮旁边的小三角处下拉选择"Run Configurations...",左侧选择要运行单元测试的任务,在右侧切换到"arguments"标签页,在"VM Options"里添加`-javaagent:`参数,下图为示例,注意应修改`testable-agent`包为与实际情况匹配的本地Maven仓库路径。
![eclipse-junit-configuration](https://testable-code.oss-cn-beijing.aliyuncs.com/eclipse-junit-configuration.png)
![eclipse-junit-configuration.png](https://img.alicdn.com/imgextra/i3/O1CN01C7DwGs1dHgVRAhh3y_!!6000000003711-2-tps-1430-1004.png)

View File

@@ -0,0 +1,46 @@
使用IntelliJ IDEA插件
---
在使用`TestableMock`工具的过程中经常涉及一些固定操作比如定义Mock容器类、复制需Mock的调用定义并创建Mock方法在执行测试时若发现Mock匹配的效果不符合预期则需要通过[自助问题排查](zh-cn/doc/troubleshooting.md)方法在运行期进行校验。通过IDE插件辅助能够将部分模式化的操作自动完成进一步优化`TestableMock`的使用体验。
目前`TestableMock`提供了 [IntelliJ IDEA](https://www.jetbrains.com/idea/) 的插件支持根据JRebel发布的 [2020](https://www.jrebel.com/blog/2020-java-technology-report#IDE) 和 [2021](https://www.jrebel.com/blog/2021-java-technology-report) Java技术趋势报告IntelliJ IDEA已成为Java开发者使用比例最高的首选IDE
## 使用方法
打开配置项在插件市场中搜索“Testable-Mock”选中`Testable-Mock Helper`插件点击“安装”然后重启IDE。
![install](https://img.alicdn.com/imgextra/i2/O1CN013twjqN2803NVtvJEP_!!6000000007869-0-tps-2342-566.jpg)
#### 1. **创建Mock容器类**
安装完插件后在任意Java类点击右键点击“生成Testable测试类”英文版为“Generate Testable TestClass”
![mock-class](https://img.alicdn.com/imgextra/i1/O1CN01gNsunh21jfVufv6EU_!!6000000007021-0-tps-546-291.jpg)
在项目`test`目录下与当前类相同的包路径位置会自动生成一个包含Mock容器类的测试类`当前类+Test`命名)。
#### 2. **创建Mock方法**
选中任意方法调用点击右键点击“复制方法为Testable-Mock”英文版为“Copy Testable Mock-Method”或“复制Testable-Mock方法到测试类”英文版为“Copy Mock-Method To TestClass”
![mock-method](https://img.alicdn.com/imgextra/i4/O1CN01K38Zsh1NrNYLlrUZ9_!!6000000001623-0-tps-642-264.jpg)
前者会将被选中调用的方法签名并转换为Mock方法定义存放到剪贴板后者则会将转换好的Mock方法直接插入到相应的Mock类里。
## 源码构建
除了通过插件市场安装,也可以直接从源码构建`Testable-Mock Helper`插件。
本地准备JDK 11环境然后执行以下命令
```bash
git clone https://github.com/zcbbpo/testable-idea
cd testable-idea
./gradlew clean build
```
构建完成后,在`build/distributions/`目录下可以找到构建好的插件zip包通过“从本地磁盘安装”菜单加载该插件。
![from-local](https://img.alicdn.com/imgextra/i3/O1CN01YMHefk26MNS4pH4ZI_!!6000000007647-0-tps-2342-516.jpg)
在此,特别感谢 @[zcbbpo](https://github.com/zcbbpo) 对`TestableMock`IntelliJ IDEA插件的贡献。

View File

@@ -31,13 +31,13 @@
该问题可以通过额外配置IDE的测试参数绕过。以IntelliJ为例打开运行菜单的"编辑配置..."选型,如图中位置①
![modify-run-configuration](https://testable-code.oss-cn-beijing.aliyuncs.com/modify-run-configuration.png)
![modify-run-configuration.png](https://img.alicdn.com/imgextra/i3/O1CN01HLlNyZ1gezVe4AOiE_!!6000000004168-2-tps-1036-184.png)
在"虚拟机参数"属性值末尾添加JavaAgent启动参数`-javaagent:${HOME}/.m2/repository/com/alibaba/testable/testable-agent/x.y.z/testable-agent-x.y.z.jar`,如图中位置②
> PS请将路径中的`x.y.z`替换成实际使用的版本号
![add-testable-javaagent](https://testable-code.oss-cn-beijing.aliyuncs.com/add-testable-javaagent.png)
![add-testable-javaagent.png](https://img.alicdn.com/imgextra/i4/O1CN01pdxC8S1R2JpXX8aOJ_!!6000000002053-2-tps-2446-486.png)
最后点击运行单元测试,如图中位置③

View File

@@ -6,8 +6,8 @@
基于上述特点,`TestableMock`设计了一种极简的Mock机制。与以往Mock工具以**类**作为Mock的定义粒度在每个测试用例里各自重复描述Mock行为的方式不同`TestableMock`让每个业务类被测类关联一组可复用的Mock方法集合使用Mock容器类承载并遵循约定优于配置的原则按照规则自动在测试运行时替换被测类中的指定方法调用。
> 实际规则约定归纳起来只有两条:
> - Mock非构造方法拷贝原方法定义到Mock容器类加`@MockMethod`注解
> - Mock构造方法拷贝原方法定义到Mock容器类返回值换成构造的类型方法名随意加`@MockContructor`注解
> - Mock非构造方法拷贝原方法定义到Mock容器类加`@MockInvoke`注解
> - Mock构造方法拷贝原方法定义到Mock容器类返回值换成构造的类型方法名随意加`@MockNew`注解
具体使用方法如下。
@@ -27,7 +27,7 @@ public class DemoTest {
### 1.1 覆写任意类的方法调用
在Mock容器类中定义一个有`@MockMethod`注解的普通方法,使它与需覆写的方法名称、参数、返回值类型完全一致,并在注解的`targetClass`参数指定该方法原本所属对象类型。
在Mock容器类中定义一个有`@MockInvoke`注解的普通方法,使它与需覆写的方法名称、参数、返回值类型完全一致,并在注解的`targetClass`参数指定该方法原本所属对象类型。
此时被测类中所有对该需覆写方法的调用将在单元测试运行时将自动被替换为对上述自定义Mock方法的调用。
@@ -36,33 +36,33 @@ public class DemoTest {
```java
// 原方法签名为`String substring(int, int)`
// 调用此方法的对象`"something"`类型为`String`
@MockMethod(targetClass = String.class)
@MockInvoke(targetClass = String.class)
private String substring(int i, int j) {
return "sub_string";
}
```
当遇到待覆写方法有重名时,可以将需覆写的方法名写到`@MockMethod`注解的`targetMethod`参数里这样Mock方法自身就可以随意命名了。
当遇到待覆写方法有重名时,可以将需覆写的方法名写到`@MockInvoke`注解的`targetMethod`参数里这样Mock方法自身就可以随意命名了。
下面这个例子展示了`targetMethod`参数的用法,其效果与上述示例相同:
```java
// 使用`targetMethod`指定需Mock的方法名
// 此方法本身现在可以随意命名,但方法参数依然需要遵循相同的匹配规则
@MockMethod(targetClass = String.class, targetMethod = "substring")
@MockInvoke(targetClass = String.class, targetMethod = "substring")
private String use_any_mock_method_name(int i, int j) {
return "sub_string";
}
```
有时在Mock方法里会需要访问发起调用的原始对象中的成员变量或是调用原始对象的其他方法。此时可以将`@MockMethod`注解中的`targetClass`参数去除,然后在方法参数列表首位增加一个类型为该方法原本所属对象类型的参数。
有时在Mock方法里会需要访问发起调用的原始对象中的成员变量或是调用原始对象的其他方法。此时可以将`@MockInvoke`注解中的`targetClass`参数去除,然后在方法参数列表首位增加一个类型为该方法原本所属对象类型的参数。
`TestableMock`约定,当`@MockMethod`注解的`targetClass`参数未定义时Mock方法的首位参数即为目标方法所属类型参数名称随意。通常为了便于代码阅读建议将此参数统一命名为`self``src`。举例如下:
`TestableMock`约定,当`@MockInvoke`注解的`targetClass`参数未定义时Mock方法的首位参数即为目标方法所属类型参数名称随意。通常为了便于代码阅读建议将此参数统一命名为`self``src`。举例如下:
```java
// Mock方法在参数列表首位增加一个类型为`String`的参数(名字随意)
// 此参数可用于获得当时的实际调用者的值和上下文
@MockMethod
@MockInvoke
private String substring(String self, int i, int j) {
// 可以直接调用原方法此时Mock方法仅用于记录调用常见于对void方法的测试
return self.substring(i, j);
@@ -81,7 +81,7 @@ private String substring(String self, int i, int j) {
```java
// 被测类型是`DemoMock`
@MockMethod(targetClass = DemoMock.class)
@MockInvoke(targetClass = DemoMock.class)
private String innerFunc(String text) {
return "mock_" + text;
}
@@ -98,7 +98,7 @@ private String innerFunc(String text) {
例如,在被测类中调用了`BlackBox`类型中的静态方法`secretBox()`,该方法签名为`BlackBox secretBox()`则Mock方法如下
```java
@MockMethod(targetClass = BlackBox.class)
@MockInvoke(targetClass = BlackBox.class)
private BlackBox secretBox() {
return new BlackBox("not_secret_box");
}
@@ -110,7 +110,7 @@ private BlackBox secretBox() {
### 1.4 覆写任意类的new操作
在Mock容器类里定义一个返回值类型为要被创建的对象类型且方法参数与要Mock的构造函数参数完全一致的方法名称随意然后加上`@MockContructor`注解。
在Mock容器类里定义一个返回值类型为要被创建的对象类型且方法参数与要Mock的构造函数参数完全一致的方法名称随意然后加上`@MockNew`注解。
此时被测类中所有用`new`创建指定类的操作并使用了与Mock方法参数一致的构造函数将被替换为对该自定义方法的调用。
@@ -119,7 +119,7 @@ private BlackBox secretBox() {
```java
// 要覆写的构造函数签名为`BlackBox(String)`
// Mock方法返回`BlackBox`类型对象,方法的名称随意起
@MockContructor
@MockNew
private BlackBox createBlackBox(String text) {
return new BlackBox("mock_" + text);
}
@@ -146,7 +146,7 @@ public void testDemo() {
在Mock方法中取出注入的参数根据情况返回不同结果
```java
@MockMethod
@MockInvoke
private Data mockDemo() {
switch((String)MOCK_CONTEXT.get("case")) {
case "data-ready":
@@ -159,13 +159,13 @@ private Data mockDemo() {
}
```
完整代码示例见`java-demo``kotlin-demo`示例项目中的`should_get_source_method_name()``should_get_test_case_name()`测试用例。
完整代码示例见`java-demo``kotlin-demo`示例项目中的`should_get_source_method_name()``should_set_mock_context()`测试用例。
### 3. 验证Mock方法被调用的顺序和参数
在测试用例中可用通过`InvokeVerifier.verify()`方法,配合`with()``withInOrder()``without()``withTimes()`等方法实现对Mock调用情况的验证。
在测试用例中可用通过`InvocationVerifier.verifyInvoked()`方法,配合`with()``withInOrder()``without()``withTimes()`等方法实现对Mock调用情况的验证。
详见[校验Mock调用](zh-cn/doc/matcher.md)文档。
详见[校验Mock调用](zh-cn/doc/invoke-matcher.md)文档。
### 4. 特别说明

View File

@@ -3,7 +3,7 @@ Mock线程池内的调用
`TestableMock`采用来自[transmittable-thread-local](https://github.com/alibaba/transmittable-thread-local)项目的`TransmittableThreadLocal`类型存储测试用例运行期的`MOCK_CONTEXT`内容和Mock方法调用过程。
当线程池中的执行对象未经过`TtlRunnable``TtlCallable`处理时,`TransmittableThreadLocal`将自动降级为与`InheritableThreadLocal`等效的类型,即只对父子线程有效,无法在线程池上下文中正常传递存储数据。因而会导致`MOCK_CONTEXT`内容丢失和`verify()`方法校验结果不正确的情况。
当线程池中的执行对象未经过`TtlRunnable``TtlCallable`处理时,`TransmittableThreadLocal`将自动降级为与`InheritableThreadLocal`等效的类型,即只对父子线程有效,无法在线程池上下文中正常传递存储数据。因而会导致`MOCK_CONTEXT`内容丢失和`verifyInvoked()`方法校验结果不正确的情况。
为此,可以启用[Testable全局配置](zh-cn/doc/javaagent-args.md)`thread.pool.enhance.enable=true`,来自动在测试启动时自动封装程序中的普通`Runnable``Callable`对象,使`TransmittableThreadLocal`恢复跨线程池存储数据的能力。
@@ -15,7 +15,7 @@ Mock线程池内的调用
```xml
<properties>
<transmittable.thread.local.version>2.12.1</transmittable.thread.local.version>
<transmittable.thread.local.version>2.14.2</transmittable.thread.local.version>
</properties>
```

View File

@@ -4,6 +4,7 @@
- [直接访问私有成员](zh-cn/doc/private-accessor.md)
- [快速构造复杂入参](zh-cn/doc/omni-constructor.md)
- [测试无返回值的方法](zh-cn/doc/test-void-method.md)
- [常用集合构建工具](zh-cn/doc/collection-tools.md)
- 使用指南
- [校验Mock调用](zh-cn/doc/invoke-matcher.md)
@@ -11,6 +12,7 @@
- [复用Mock类与方法](zh-cn/doc/mock-method-reusing.md)
- [使用MockWith注解](zh-cn/doc/use-mock-with.md)
- [使用包路径映射](zh-cn/doc/use-package-mapping.md)
- [使用IntelliJ插件](zh-cn/doc/use-intellij-plugin.md)
- [常见使用问题](zh-cn/doc/frequently-asked-questions.md)
- [Mock线程池内的调用](zh-cn/doc/with-thread-pool.md)
- [在IDE运行单元测试](zh-cn/doc/use-in-ide.md)

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-parent</artifactId>
<version>0.6.8</version>
<version>0.7.9</version>
<relativePath>../testable-parent</relativePath>
</parent>
<artifactId>testable-agent</artifactId>
@@ -14,8 +14,8 @@
<name>testable-agent</name>
<properties>
<asm.lib.version>8.0.1</asm.lib.version>
<junit.version>5.6.2</junit.version>
<asm.lib.version>9.3</asm.lib.version>
<junit.version>5.9.1</junit.version>
</properties>
<dependencies>
@@ -123,6 +123,9 @@
<goals>
<goal>jar</goal>
</goals>
<configuration>
<additionalOptions>-Xdoclint:none</additionalOptions>
</configuration>
</execution>
</executions>
</plugin>
@@ -137,6 +140,13 @@
<goals>
<goal>sign</goal>
</goals>
<configuration>
<!-- Prevent `gpg` from using pinentry programs -->
<gpgArguments>
<arg>--pinentry-mode</arg>
<arg>loopback</arg>
</gpgArguments>
</configuration>
</execution>
</executions>
</plugin>

View File

@@ -7,17 +7,20 @@ public class ConstPool {
public static final String FIELD_TARGET_METHOD = "targetMethod";
public static final String FIELD_TARGET_CLASS = "targetClass";
public static final String FIELD_TARGET_CLASS_NAME = "targetClassName";
public static final String FIELD_SCOPE = "scope";
public static final String FIELD_VALUE = "value";
public static final String FIELD_INHERITS = "inherits";
public static final String PROPERTY_USER_DIR = "user.dir";
public static final String PROPERTY_TEMP_DIR = "java.io.tmpdir";
public static final String MOCK_CONTAINER = "com.alibaba.testable.core.annotation.MockContainer";
public static final String MOCK_WITH = "com.alibaba.testable.core.annotation.MockWith";
public static final String DUMP_TO = "com.alibaba.testable.core.annotation.DumpTo";
public static final String MOCK_DIAGNOSE = "com.alibaba.testable.core.annotation.MockDiagnose";
public static final String MOCK_METHOD = "com.alibaba.testable.core.annotation.MockMethod";
public static final String MOCK_CONSTRUCTOR = "com.alibaba.testable.core.annotation.MockConstructor";
public static final String MOCK_INVOKE = "com.alibaba.testable.core.annotation.MockInvoke";
public static final String MOCK_NEW = "com.alibaba.testable.core.annotation.MockNew";
public static final String CGLIB_CLASS_PATTERN = "$$EnhancerBy";
public static final String KOTLIN_POSTFIX_COMPANION = "$Companion";

View File

@@ -19,7 +19,9 @@ abstract public class BaseClassHandler implements Opcodes {
ClassNode cn = new ClassNode();
cr.accept(cn, 0);
transform(cn);
ClassWriter cw = new ClassWriter( 0);
// flag 1 was auto compute max
ClassWriter cw = new ClassWriter( ClassWriter.COMPUTE_MAXS);
cn.accept(cw);
return cw.toByteArray();
}

View File

@@ -1,6 +1,5 @@
package com.alibaba.testable.agent.handler;
import com.alibaba.testable.agent.constant.ConstPool;
import com.alibaba.testable.agent.tool.ImmutablePair;
import com.alibaba.testable.agent.util.*;
import com.alibaba.testable.core.model.MockScope;
@@ -10,12 +9,16 @@ import org.objectweb.asm.Label;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.alibaba.testable.agent.constant.ByteCodeConst.TYPE_ARRAY;
import static com.alibaba.testable.agent.constant.ByteCodeConst.TYPE_CLASS;
import static com.alibaba.testable.agent.constant.ConstPool.CLASS_OBJECT;
import static com.alibaba.testable.agent.constant.ConstPool.*;
import static com.alibaba.testable.agent.util.MockInvokeUtil.*;
import static com.alibaba.testable.core.constant.ConstPool.CONSTRUCTOR;
import static com.alibaba.testable.core.util.CollectionUtil.fastListOf;
/**
* @author flin
@@ -63,9 +66,69 @@ public class MockClassHandler extends BaseClassWithContextHandler {
handleTestableUtil(mn);
}
}
// should inject inherited at last to avoid above transfers
injectInheritedMockMethods(cn);
LogUtil.diagnose(" Found %d mock methods", mockMethodCount);
}
/**
* add member field of inherited mock class by @MockContainer annotation
*/
private void injectInheritedMockMethods(ClassNode cn) {
List<Type> inheritedTypes = new ArrayList<Type>();
AnnotationNode mockContainerAnnotation = AnnotationUtil.getClassAnnotation(cn, MOCK_CONTAINER);
if (mockContainerAnnotation != null) {
inheritedTypes.addAll(AnnotationUtil.getAnnotationParameter(mockContainerAnnotation, FIELD_INHERITS,
Collections.<Type>emptyList(), List.class));
}
for (Type inheritedType : inheritedTypes) {
String className = inheritedType.getClassName();
ClassNode inheritedClassNode = ClassUtil.getClassNode(className);
if (inheritedClassNode == null) {
throw new IllegalArgumentException("Failed to load class " + className);
}
for (MethodNode mn : inheritedClassNode.methods) {
if (mn.visibleAnnotations == null) {
continue;
}
if (ClassUtil.hasMethod(cn, mn)) {
LogUtil.warn("Method '%s' with same signature already exists in class %s, skipped", mn.name, cn.name);
continue;
}
for (AnnotationNode an : mn.visibleAnnotations) {
if ((ClassUtil.toByteCodeClassName(MOCK_INVOKE)).equals(an.desc)) {
String targetClassName = getTargetClassName(an);
String targetMethod = AnnotationUtil.getAnnotationParameter(an, FIELD_TARGET_METHOD, null, String.class);
String desc = (targetClassName == null) ? mn.desc :
MethodUtil.addParameterAtBegin(mn.desc, ClassUtil.toByteCodeClassName(targetClassName));
String name = (targetMethod == null) ? mn.name : targetMethod;
MethodNode mockMethod = new MethodNode(ACC_PUBLIC, name, desc, null, null);
List<Byte> parameters = MethodUtil.getParameterTypes(mn.desc);
int maxStack = 2;
InsnList il = new InsnList();
il.add(new MethodInsnNode(INVOKESTATIC, ClassUtil.toSlashSeparatedName(className),
GET_TESTABLE_REF, VOID_ARGS + ClassUtil.toByteCodeClassName(className), false));
il.add(new VarInsnNode(ALOAD, 1));
for (int pi = 2; pi < parameters.size() + 2; pi++) {
ImmutablePair<Integer, Integer> codeAndStack = BytecodeUtil.getLoadParameterByteCode(parameters.get(pi - 2));
il.add(new VarInsnNode(codeAndStack.left, pi));
maxStack += codeAndStack.right;
}
il.add(new MethodInsnNode(INVOKEVIRTUAL, ClassUtil.toSlashSeparatedName(className), mn.name, desc, false));
il.add(new InsnNode(ARETURN));
mockMethod.instructions = il;
mockMethod.maxStack = maxStack;
mockMethod.maxLocals = 2 + parameters.size();
mockMethod.visibleAnnotations = fastListOf(new AnnotationNode(ClassUtil.toByteCodeClassName(MOCK_INVOKE)));
cn.methods.add(mockMethod);
} else if ((ClassUtil.toByteCodeClassName(MOCK_NEW)).equals(an.desc)) {
// TODO: should also support MockNew annotation
}
}
}
}
}
/**
* add method to fetch singleton instance of this mock class
*/
@@ -93,17 +156,17 @@ public class MockClassHandler extends BaseClassWithContextHandler {
}
/**
* put targetClass parameter in @MockMethod to first parameter of the mock method
* put targetClass parameter in @MockInvoke to first parameter of the mock method
*/
private void unfoldTargetClass(MethodNode mn) {
String targetClassName = null;
for (AnnotationNode an : mn.visibleAnnotations) {
if (ClassUtil.toByteCodeClassName(ConstPool.MOCK_METHOD).equals(an.desc)) {
Type type = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_TARGET_CLASS, null, Type.class);
if (type != null) {
targetClassName = ClassUtil.toByteCodeClassName(type.getClassName());
if (ClassUtil.toByteCodeClassName(MOCK_INVOKE).equals(an.desc)) {
String name = getTargetClassName(an);
if (name != null) {
targetClassName = ClassUtil.toByteCodeClassName(name);
}
AnnotationUtil.removeAnnotationParameter(an, ConstPool.FIELD_TARGET_CLASS);
AnnotationUtil.removeAnnotationParameters(an, FIELD_TARGET_CLASS, FIELD_TARGET_CLASS_NAME);
}
}
if (targetClassName != null) {
@@ -190,7 +253,10 @@ public class MockClassHandler extends BaseClassWithContextHandler {
if (VOID_RES.equals(returnType)) {
il.add(new InsnNode(POP));
il.add(new InsnNode(RETURN));
} else if (returnType.charAt(0) == TYPE_ARRAY || returnType.charAt(0) == TYPE_CLASS) {
} else if (returnType.charAt(0) == TYPE_ARRAY) {
il.add(new TypeInsnNode(CHECKCAST, returnType));
il.add(new InsnNode(ARETURN));
} else if (returnType.charAt(0) == TYPE_CLASS) {
il.add(new TypeInsnNode(CHECKCAST, ClassUtil.toSlashSeparateJavaStyleName(returnType)));
il.add(new InsnNode(ARETURN));
} else {
@@ -208,11 +274,11 @@ public class MockClassHandler extends BaseClassWithContextHandler {
String methodName = mn.name;
for (AnnotationNode an : mn.visibleAnnotations) {
if (isMockMethodAnnotation(an)) {
String name = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_TARGET_METHOD, null, String.class);
String name = AnnotationUtil.getAnnotationParameter(an, FIELD_TARGET_METHOD, null, String.class);
if (name != null) {
methodName = name;
}
} else if (isMockConstructorAnnotation(an)) {
} else if (isMockNewAnnotation(an)) {
methodName = CONSTRUCTOR;
}
}
@@ -226,8 +292,8 @@ public class MockClassHandler extends BaseClassWithContextHandler {
private boolean isGlobalScope(MethodNode mn) {
for (AnnotationNode an : mn.visibleAnnotations) {
if (isMockMethodAnnotation(an) || isMockConstructorAnnotation(an)) {
MockScope scope = AnnotationUtil.getAnnotationParameter(an, ConstPool.FIELD_SCOPE,
if (isMockMethodAnnotation(an) || isMockNewAnnotation(an)) {
MockScope scope = AnnotationUtil.getAnnotationParameter(an, FIELD_SCOPE,
GlobalConfig.defaultMockScope, MockScope.class);
if (scope.equals(MockScope.GLOBAL)) {
return true;
@@ -243,12 +309,16 @@ public class MockClassHandler extends BaseClassWithContextHandler {
}
for (AnnotationNode an : mn.visibleAnnotations) {
if (isMockMethodAnnotation(an)) {
if (hasDuplicatedTargetClass(an)) {
throw new IllegalArgumentException("Mock method " + mn.name + " could not use both '"
+ FIELD_TARGET_CLASS + "' and '" + FIELD_TARGET_CLASS_NAME + "' parameter");
}
if (LogUtil.isVerboseEnabled()) {
LogUtil.verbose(" Mock method \"%s\" as \"%s\"", mn.name, MethodUtil.toJavaMethodDesc(
getTargetMethodOwner(mn, an), getTargetMethodName(mn, an), getTargetMethodDesc(mn, an)));
}
return true;
} else if (isMockConstructorAnnotation(an)) {
} else if (isMockNewAnnotation(an)) {
if (LogUtil.isVerboseEnabled()) {
LogUtil.verbose(" Mock constructor \"%s\" as \"%s\"", mn.name, MethodUtil.toJavaMethodDesc(
ClassUtil.toJavaStyleClassName(MethodUtil.getReturnType(mn.desc)), mn.desc));
@@ -260,29 +330,26 @@ public class MockClassHandler extends BaseClassWithContextHandler {
}
private String getTargetMethodOwner(MethodNode mn, AnnotationNode mockMethodAnnotation) {
Type type = AnnotationUtil.getAnnotationParameter(mockMethodAnnotation, ConstPool.FIELD_TARGET_CLASS,
null, Type.class);
return type == null ? MethodUtil.getFirstParameter(mn.desc) : type.getClassName();
String targetClassName = getTargetClassName(mockMethodAnnotation);
return targetClassName == null ? MethodUtil.getFirstParameter(mn.desc) : targetClassName;
}
private String getTargetMethodName(MethodNode mn, AnnotationNode mockMethodAnnotation) {
String name = AnnotationUtil.getAnnotationParameter(mockMethodAnnotation, ConstPool.FIELD_TARGET_METHOD,
String name = AnnotationUtil.getAnnotationParameter(mockMethodAnnotation, FIELD_TARGET_METHOD,
null, String.class);
return name == null ? mn.name : name;
}
private String getTargetMethodDesc(MethodNode mn, AnnotationNode mockMethodAnnotation) {
Type type = AnnotationUtil.getAnnotationParameter(mockMethodAnnotation, ConstPool.FIELD_TARGET_CLASS,
null, Type.class);
return type == null ? MethodUtil.removeFirstParameter(mn.desc) : mn.desc;
return hasTargetClassParameter(mockMethodAnnotation) ? mn.desc : MethodUtil.removeFirstParameter(mn.desc);
}
private boolean isMockConstructorAnnotation(AnnotationNode an) {
return ClassUtil.toByteCodeClassName(ConstPool.MOCK_CONSTRUCTOR).equals(an.desc);
private boolean isMockNewAnnotation(AnnotationNode an) {
return ClassUtil.toByteCodeClassName(MOCK_NEW).equals(an.desc);
}
private boolean isMockMethodAnnotation(AnnotationNode an) {
return ClassUtil.toByteCodeClassName(ConstPool.MOCK_METHOD).equals(an.desc);
return ClassUtil.toByteCodeClassName(MOCK_INVOKE).equals(an.desc);
}
private void injectInvokeRecorder(MethodNode mn) {
@@ -324,11 +391,11 @@ public class MockClassHandler extends BaseClassWithContextHandler {
private boolean isMockForConstructor(MethodNode mn) {
for (AnnotationNode an : mn.visibleAnnotations) {
String annotationName = ClassUtil.toJavaStyleClassName(an.desc);
if (ConstPool.MOCK_CONSTRUCTOR.equals(annotationName)) {
if (MOCK_NEW.equals(annotationName)) {
return true;
} else if (ConstPool.MOCK_METHOD.equals(annotationName)) {
} else if (MOCK_INVOKE.equals(annotationName)) {
String method = AnnotationUtil.getAnnotationParameter
(an, ConstPool.FIELD_TARGET_METHOD, null, String.class);
(an, FIELD_TARGET_METHOD, null, String.class);
if (CONSTRUCTOR.equals(method)) {
return true;
}

View File

@@ -2,17 +2,26 @@ package com.alibaba.testable.agent.handler;
import com.alibaba.testable.agent.handler.test.JUnit4Framework;
import com.alibaba.testable.agent.handler.test.JUnit5Framework;
import com.alibaba.testable.agent.util.AnnotationUtil;
import com.alibaba.testable.agent.util.BytecodeUtil;
import com.alibaba.testable.agent.util.ClassUtil;
import com.alibaba.testable.agent.util.CollectionUtil;
import com.alibaba.testable.core.tool.CollectionTool;
import com.alibaba.testable.core.util.CollectionUtil;
import com.alibaba.testable.core.util.LogUtil;
import com.alibaba.testable.core.util.StringUtil;
import com.alibaba.testable.core.util.TypeUtil;
import org.objectweb.asm.Label;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.alibaba.testable.agent.constant.ConstPool.CLASS_OBJECT;
import static com.alibaba.testable.core.constant.ConstPool.CONSTRUCTOR;
import static com.alibaba.testable.core.constant.ConstPool.THIS_REF;
import static com.alibaba.testable.core.util.CollectionUtil.contains;
import static com.alibaba.testable.core.tool.CollectionTool.*;
/**
* @author flin
@@ -23,22 +32,28 @@ public class OmniClassHandler extends BaseClassHandler {
private static final String IGNORE = "ignore";
private static final String METHOD_START = "(";
private static final String VOID_METHOD_END = ")V";
private static final String VOID_METHOD = "()V";
private static final String ENABLE_CONFIGURATION = "Lorg/springframework/context/annotation/Configuration;";
private static final String CLASS_ABSTRACT_COLLECTION = "java/util/AbstractCollection";
private static final String CLASS_NUMBER = "java/lang/Number";
private static final String CLASS_HASH_SET = "java/util/HashSet";
private static final String ENABLE_CONFIGURATION = "org.springframework.context.annotation.Configuration";
private static final String CLASS_OMNI_CONSTRUCTOR = "com/alibaba/testable/core/tool/OmniConstructor";
public static final String CLASS_CONSTRUCTION_OPTION = "com/alibaba/testable/core/model/ConstructionOption";
public static final String METHOD_NEW_INSTANCE = "newInstance";
public static final String METHOD_DESC_NEW_INSTANCE = "(Ljava/lang/Class;[Lcom/alibaba/testable/core/model/ConstructionOption;)Ljava/lang/Object;";
// below classes are loaded before OmniClassHandler, cannot be instrumented
// map of class name to constructor parameters
private static final Map<String, String[]> PRELOADED_CLASSES = mapOf(
entryOf(CLASS_OBJECT, CollectionTool.<String>arrayOf())
);
private static final String[] JUNIT_TEST_ANNOTATIONS = new String[] {
JUnit4Framework.ANNOTATION_TEST, JUnit5Framework.ANNOTATION_TEST, JUnit5Framework.ANNOTATION_PARAMETERIZED_TEST
};
private static final String[] UNREACHABLE_CLASSES = new String[] {
CLASS_ABSTRACT_COLLECTION, CLASS_NUMBER, CLASS_HASH_SET
};
private static final Map<String, Class<?>[]> constructorParameterCache = new HashMap<String, Class<?>[]>();
@Override
protected void transform(ClassNode cn) {
if (isInterfaceOrAtom(cn) || isJunitTestClass(cn) || isUninstantiableClass(cn) || hasSpecialAnnotation(cn)) {
if (isInterfaceOrAtom(cn) || isUniqueConstructorClass(cn) || isUninstantiableClass(cn) ||
AnnotationUtil.getClassAnnotation(cn, ENABLE_CONFIGURATION) != null) {
return;
}
addConstructorWithVoidTypeParameter(cn);
@@ -49,29 +64,56 @@ public class OmniClassHandler extends BaseClassHandler {
METHOD_START + ClassUtil.toByteCodeClassName(VOID_TYPE) + VOID_METHOD_END, null, null);
LabelNode start = new LabelNode(new Label());
LabelNode end = new LabelNode(new Label());
if (cn.superName.equals(CLASS_OBJECT)) {
constructor.instructions = invokeSuperWithoutParameter(start, end);
constructor.localVariables = createLocalVariables(cn, start, end);
constructor.maxStack = 1;
int extraParameterCount = 2;
if (PRELOADED_CLASSES.containsKey(cn.superName)) {
constructor.instructions = invokeSuperWithoutTestableParameter(cn.superName, PRELOADED_CLASSES.get(cn.superName), start, end);
extraParameterCount = PRELOADED_CLASSES.get(cn.superName).length;
} else if (cn.superName.startsWith("java/")) {
try {
Class<?>[] constructorParameterTypes = constructorParameterCache.get(cn.superName);
if (constructorParameterTypes == null) {
Class<?> superClazz = Class.forName(ClassUtil.toDotSeparatedName(cn.superName));
constructorParameterTypes = TypeUtil.getBestConstructor(superClazz).getParameterTypes();
constructorParameterCache.put(superClazz.getName(), constructorParameterTypes);
}
if (constructorParameterTypes.length == 0) {
constructor.instructions = invokeSuperWithoutTestableParameter(cn.superName, new String[0], start, end);
extraParameterCount = 0;
} else if (constructorParameterTypes.length == 1 &&
constructorParameterTypes[0].equals(Void.class)) {
constructor.instructions = invokeSuperWithTestableVoidParameter(cn.superName, start, end);
} else {
constructor.instructions = invokeSuperWithoutTestableParameter(cn.superName,
toByteCodeClassNames(constructorParameterTypes), start, end);
extraParameterCount = constructorParameterTypes.length;
}
} catch (ClassNotFoundException e) {
LogUtil.warn("[OmniConstructor] Failed to load class " + cn.superName);
constructor.instructions = invokeSuperWithoutTestableParameter(cn.superName, new String[0], start, end);
}
} else {
constructor.instructions = invokeSuperWithTestableVoidParameter(cn, start, end);
constructor.localVariables = createLocalVariables(cn, start, end);
constructor.maxStack = 3;
constructor.instructions = invokeSuperWithTestableVoidParameter(cn.superName, start, end);
}
constructor.localVariables = createLocalVariables(cn, start, end);
constructor.maxStack = 1 + extraParameterCount;
constructor.maxLocals = 2;
cn.methods.add(constructor);
}
private boolean hasSpecialAnnotation(ClassNode cn) {
if (cn.visibleAnnotations == null) {
return false;
}
for (AnnotationNode an : cn.visibleAnnotations) {
if (an.desc.equals(ENABLE_CONFIGURATION)) {
return true;
private String[] toByteCodeClassNames(Class<?>[] classes) {
String[] names = new String[classes.length];
for (int i = 0; i < names.length; i++) {
if (classes[i].isPrimitive()) {
names[i] = BytecodeUtil.PRIMITIVE_TYPE_NAME_MAP.get(classes[i].getName());
} else if (classes[i].isArray()) {
names[i] = ClassUtil.toSlashSeparatedName(classes[i].getName());
} else {
names[i] = ClassUtil.toByteCodeClassName(classes[i].getName());
}
}
return false;
return names;
}
private boolean isUninstantiableClass(ClassNode cn) {
@@ -89,14 +131,22 @@ public class OmniClassHandler extends BaseClassHandler {
return (cn.access & ACC_INTERFACE) != 0 || cn.superName == null || VOID_TYPE.equals(cn.name);
}
private boolean isJunitTestClass(ClassNode cn) {
private boolean isUniqueConstructorClass(ClassNode cn) {
// elastic plugin class should contain only one constructor
if ("org/elasticsearch/plugins/Plugin".equals(cn.superName)) {
return true;
}
// should skip mockito generated class
if (cn.name.contains("$MockitoMock$")) {
return true;
}
// junit require test class contains only one constructor
for (MethodNode mn : cn.methods) {
if (mn.visibleAnnotations == null) {
continue;
}
for (AnnotationNode an : mn.visibleAnnotations) {
if (contains(JUNIT_TEST_ANNOTATIONS, an.desc)) {
if (CollectionUtil.contains(JUNIT_TEST_ANNOTATIONS, an.desc)) {
return true;
}
}
@@ -104,34 +154,38 @@ public class OmniClassHandler extends BaseClassHandler {
return false;
}
private InsnList invokeSuperWithoutParameter(LabelNode start, LabelNode end) {
private InsnList invokeSuperWithoutTestableParameter(String superName, String[] parameters, LabelNode start, LabelNode end) {
InsnList il = new InsnList();
il.add(start);
il.add(new VarInsnNode(ALOAD, 0));
il.add(new MethodInsnNode(INVOKESPECIAL, CLASS_OBJECT, CONSTRUCTOR, VOID_METHOD, false));
for (String p : parameters) {
il.add(new LdcInsnNode(Type.getType(p)));
il.add(new InsnNode(ICONST_0));
il.add(new TypeInsnNode(ANEWARRAY, CLASS_CONSTRUCTION_OPTION));
il.add(new MethodInsnNode(INVOKESTATIC, CLASS_OMNI_CONSTRUCTOR, METHOD_NEW_INSTANCE, METHOD_DESC_NEW_INSTANCE, false));
il.add(new TypeInsnNode(CHECKCAST, (p.startsWith("[")) ? p : ClassUtil.toSlashSeparateJavaStyleName(p)));
}
il.add(new MethodInsnNode(INVOKESPECIAL, superName, CONSTRUCTOR,
METHOD_START + StringUtil.join("", parameters) + VOID_METHOD_END, false));
il.add(new InsnNode(RETURN));
il.add(end);
return il;
}
private InsnList invokeSuperWithTestableVoidParameter(ClassNode cn, LabelNode start, LabelNode end) {
private InsnList invokeSuperWithTestableVoidParameter(String superName, LabelNode start, LabelNode end) {
InsnList il = new InsnList();
il.add(start);
il.add(new VarInsnNode(ALOAD, 0));
if (contains(UNREACHABLE_CLASSES, cn.superName)) {
il.add(new MethodInsnNode(INVOKESPECIAL, cn.superName, CONSTRUCTOR, VOID_METHOD, false));
} else {
il.add(new VarInsnNode(ALOAD, 1));
il.add(new MethodInsnNode(INVOKESPECIAL, cn.superName, CONSTRUCTOR,
METHOD_START + ClassUtil.toByteCodeClassName(VOID_TYPE) + VOID_METHOD_END, false));
}
il.add(new VarInsnNode(ALOAD, 1));
il.add(new MethodInsnNode(INVOKESPECIAL, superName, CONSTRUCTOR,
METHOD_START + ClassUtil.toByteCodeClassName(VOID_TYPE) + VOID_METHOD_END, false));
il.add(new InsnNode(RETURN));
il.add(end);
return il;
}
private List<LocalVariableNode> createLocalVariables(ClassNode cn, LabelNode start, LabelNode end) {
return CollectionUtil.listOf(
return listOf(
new LocalVariableNode(THIS_REF, ClassUtil.toByteCodeClassName(cn.name), null, start, end, 0),
new LocalVariableNode(IGNORE, ClassUtil.toByteCodeClassName(VOID_TYPE), null, start, end, 1)
);

Some files were not shown because too many files have changed in this diff Show More