Compare commits

...

196 Commits
v0.4.4 ... 0.6

Author SHA1 Message Date
金戟
e370114b01 release v0.6 2021-03-27 20:05:21 +08:00
金戟
26d3178840 support set dump path for single class 2021-03-27 00:31:02 +08:00
金戟
55ccc385d5 remove unnecessary mock class reference in test class handler 2021-03-26 22:26:12 +08:00
金戟
20b346ed34 remove diagnose parameter in MockWith annotation 2021-03-26 20:20:45 +08:00
金戟
00055a4626 always print more error information 2021-03-26 01:12:52 +08:00
金戟
27174afa93 implement fall-fast anti-refactor machenism 2021-03-26 01:12:30 +08:00
金戟
3d6d3c9dd1 skip $jacocoData field 2021-03-25 13:10:49 +08:00
金戟
df7e641a0c update doc to fit version 0.6 2021-03-24 23:51:19 +08:00
金戟
3acf341d1f add kotlin test case for omni 2021-03-24 13:23:02 +08:00
金戟
52878364ee don't travel into basic class 2021-03-24 13:17:15 +08:00
金戟
9871a96db0 create omni constructor correctly 2021-03-24 00:34:17 +08:00
金戟
9b56e3d64a fit common collection interfaces 2021-03-23 21:29:54 +08:00
金戟
ea8f305152 handle circle reference 2021-03-23 00:00:48 +08:00
金戟
3feba7fdfa allow non-public mock class 2021-03-22 14:57:59 +08:00
金戟
9064f7a582 better demo for omni methods 2021-03-22 00:41:19 +08:00
金戟
b062dbc592 move private accessor to tool package 2021-03-21 07:43:22 +08:00
金戟
a623bd0a33 Merge branch 'master' into 0.6
* master:
  update faq about how to mock inner class
2021-03-20 20:25:10 +08:00
金戟
a54bb33c09 support quick create array instance 2021-03-20 20:23:23 +08:00
金戟
bbc41f2987 fix omni get path with index 2021-03-20 18:02:01 +08:00
金戟
7b9a0964bc use k8s-like model for omni demos 2021-03-20 17:30:10 +08:00
金戟
a0dae0dfe1 support wildcard match 2021-03-20 14:34:19 +08:00
金戟
f54a6ce022 handle array in query path correctly 2021-03-20 00:50:30 +08:00
金戟
606760d632 refactor test code, and omni constructor test 2021-03-18 23:51:06 +08:00
金戟
206fc5486e support access array element by index 2021-03-18 00:15:01 +08:00
金戟
af3af6b272 handle array and primary types 2021-03-17 19:26:50 +08:00
金戟
fe7420c99a implement basic omni instance construction 2021-03-17 16:24:20 +08:00
金戟
7857782125 do omni transform 2021-03-17 14:24:16 +08:00
金戟
f776deff0e update faq about how to mock inner class 2021-03-16 22:19:16 +08:00
金戟
328296ae3a complete get and set by path of common class 2021-03-16 14:31:37 +08:00
金戟
a5038320b4 convert query path to match pattern 2021-03-16 11:50:22 +08:00
金戟
bdd16c0948 generate member index of specified class 2021-03-15 23:51:21 +08:00
金戟
dce1accafb fix class reference of frame full operation 2021-03-15 19:14:26 +08:00
Fan Lin
2865093fef Merge pull request #116 from mikero8/fix-readme
Update README_EN.md
2021-03-14 17:02:18 +08:00
Mikero
5f69b21532 Update README_EN.md
Fix an issue in English README
2021-03-13 09:14:41 -08:00
金戟
250a1beaf9 bump to version 0.5.2 2021-03-13 09:35:33 +08:00
金戟
323ab398b2 fix check-cast byte code format 2021-03-13 00:38:00 +08:00
金戟
edd4c24d88 duplication check is unnecessary 2021-03-09 15:54:05 +08:00
金戟
4995bbd9a9 support access private member of super class 2021-03-09 14:21:05 +08:00
金戟
121536a337 support mocking invocation inside an inner class 2021-03-05 00:04:44 +08:00
金戟
b163152c2f better diagnose print 2021-03-04 22:13:10 +08:00
金戟
5897e087c8 print exception location 2021-03-04 11:16:49 +08:00
金戟
6f079db3f3 move demo code out of testable package 2021-03-04 11:09:12 +08:00
金戟
40073ad784 should handle void return type 2021-03-04 10:00:07 +08:00
金戟
e46ec3d98a release v0.5.1 2021-03-03 09:46:21 +08:00
金戟
1849cf85c5 use kotlin style method descriptor 2021-03-02 23:42:04 +08:00
金戟
f8450d7047 use pre-check to reduce unnecessary verbose parameter calculation 2021-03-02 23:33:17 +08:00
金戟
196dffc73d print diagnose verbose using java style descriptor 2021-03-02 23:18:27 +08:00
金戟
1ff6ec5961 support junit 5 parameterized test 2021-03-01 23:47:50 +08:00
金戟
e50633b884 fit for ternary operation in object constructor parameter 2021-02-27 00:59:22 +08:00
金戟
2976ca0211 print diagnose descriptor in java-like format 2021-02-26 14:02:58 +08:00
金戟
24c6a9cc5c validate mock method before use 2021-02-25 13:48:33 +08:00
金戟
9e7ceb2dc1 fix diagnose setup when using MockWith annotation 2021-02-24 21:29:48 +08:00
金戟
8dfd0b4516 handle bytecode jump opcode 2021-02-24 21:07:56 +08:00
金戟
190cf4cc3c should handle frame byte code when mock substitutions 2021-02-24 14:31:50 +08:00
金戟
d49f526376 add placeholder for english docs 2021-02-23 13:12:23 +08:00
金戟
f01ff10fba narrow down Exception scope when possible 2021-02-22 15:56:50 +08:00
金戟
f64b07e9da add doc for mock design 2021-02-21 14:32:32 +08:00
金戟
f96d272f97 release 0.5.0 2021-02-21 13:56:22 +08:00
金戟
b1e6ad58cc fix java doc error 2021-02-21 13:55:48 +08:00
金戟
48d6ef328b refactor docs 2021-02-21 11:58:32 +08:00
金戟
84df2a0fd8 adapt english docs to v0.5 2021-02-21 11:12:38 +08:00
金戟
f9621a6f2e allow default mock with target 2021-02-21 10:49:30 +08:00
金戟
55ccabbb39 add one to multi test case and refactor demo packages 2021-02-21 10:20:59 +08:00
金戟
cfc28467bb stack size increase should be cumulated 2021-02-20 22:38:22 +08:00
金戟
23a0a0cd90 allow lower case global parameter value 2021-02-20 09:04:43 +08:00
金戟
a1086ff402 NoVerify class is unnecessary 2021-02-20 09:04:43 +08:00
金戟
6237e19a32 support modify default mock scope 2021-02-20 09:04:43 +08:00
金戟
df73dac54f add 0.5 zh-cn docs 2021-02-20 09:03:44 +08:00
金戟
f660ccb88f always look for outer mock class 2021-02-19 17:16:01 +08:00
金戟
82d87f3447 use mock class to setup diagnose 2021-02-19 13:43:55 +08:00
金戟
65cc631d39 refactor and reduce stack size 2021-02-18 19:08:51 +08:00
金戟
bdd99577c4 fit for static mock method 2021-02-18 18:15:11 +08:00
金戟
8be5550331 extra <init> constant to core package 2021-02-18 13:25:51 +08:00
金戟
6a5c817a3a fit for frame-full opcode used by jacoco 2021-02-18 12:45:10 +08:00
金戟
c76ed2d84a implement origin-invoker method 2021-02-18 12:10:08 +08:00
金戟
14549040d9 fit invoke from test case without testable 2021-02-17 23:24:14 +08:00
金戟
03ec857cd0 invoke origin method if not associated 2021-02-17 22:59:24 +08:00
金戟
b4a29e7e8e mock target is always in parameter when recording 2021-02-17 20:04:26 +08:00
金戟
02c74299f4 prepare mock association util 2021-02-17 19:53:12 +08:00
金戟
4c6fc5b228 add association check at beginning of mock method 2021-02-17 18:48:43 +08:00
金戟
6d6e2ecb7f add mock method scope 2021-02-17 17:59:13 +08:00
金戟
c364e96f77 add association check method 2021-02-17 17:38:30 +08:00
金戟
dab1d36a81 refactor transformer 2021-02-17 13:45:57 +08:00
金戟
01bd676df7 aload 0 should be kept 2021-02-16 18:27:27 +08:00
金戟
02d7fb6e91 fix mock method signature print in verbose log 2021-02-16 17:58:04 +08:00
金戟
d8ffcacdaf always unfold target class parameter of MockMethod annotation to method parameter 2021-02-16 16:09:05 +08:00
金戟
be53ea2d9c suggest use MockDiagnose to set log level 2021-02-16 10:47:18 +08:00
金戟
9eb8682ec0 warn for non-static inner mock class 2021-02-15 14:51:28 +08:00
金戟
2a54fe70dd mock context can be null 2021-02-15 14:39:43 +08:00
金戟
790409a336 allow use MockWith annotation specify empty mock class 2021-02-15 14:26:01 +08:00
金戟
189ef43565 support mock class inheriting 2021-02-15 14:08:56 +08:00
金戟
34f1dc8f73 use treatAs instead of isSrc 2021-02-15 13:14:04 +08:00
金戟
b60ebf2f2c fix NoClassDefFoundError while using MockWith annotation 2021-02-14 18:38:51 +08:00
金戟
d95cba6d37 simplify mock context init 2021-02-14 13:50:24 +08:00
金戟
5ef06c4bde remove mock context after test 2021-02-14 13:22:28 +08:00
金戟
8814276d63 remove old mock context holder 2021-02-13 17:47:19 +08:00
金戟
374a4d5442 use mock context to store invocation record 2021-02-13 16:26:47 +08:00
金戟
9a22f361f3 use mock context to fetch current test case 2021-02-12 01:02:34 +08:00
金戟
94c5aa8621 use transmittable thread local to record test case context 2021-02-13 12:17:53 +08:00
金戟
74e3cf16a4 use standalone mock class in demos 2021-02-11 14:56:08 +08:00
金戟
19f28c32db fix test case mark and add unnullable map 2021-02-11 13:29:08 +08:00
金戟
affa56d057 use global mock context parameter 2021-02-11 10:15:06 +08:00
金戟
f1edf92626 move invoke recorder injection to mock class 2021-02-10 22:42:00 +08:00
金戟
6f9accf319 invoke via mock singleton instance 2021-02-10 22:30:38 +08:00
金戟
cefc5fb1df inject singleton field and method to mock class 2021-02-10 13:09:56 +08:00
金戟
948878687c add mock class handler 2021-02-10 10:36:51 +08:00
金戟
d626906d28 thread mark not actually needed 2021-02-09 22:27:30 +08:00
金戟
705ba796f1 remove deprecated TestableMock annotation 2021-02-09 16:04:13 +08:00
金戟
7e44a66205 add doc for new parameters and verbose diagnosis level 2021-02-07 16:43:44 +08:00
金戟
d57dedb6fc release v0.4.12 2021-02-07 10:41:07 +08:00
金戟
d860684e91 add switch to disable private access target existence check 2021-02-06 21:56:36 +08:00
金戟
7bbdf82a41 support specify package prefix of class to be mocked 2021-02-06 11:31:51 +08:00
金戟
126c7257d8 fix out of index exception when mocking native method 2021-02-06 10:44:51 +08:00
金戟
adfba4bac0 support verbose log for self diagnosing 2021-02-05 22:30:04 +08:00
金戟
2c18ea12ed use space instead of tab 2021-02-04 17:59:26 +08:00
金戟
d4bce13918 remove note about 0.4.5 version 2021-02-03 18:03:07 +08:00
金戟
b23b8b98b3 add self troubleshooting information 2021-02-02 14:24:12 +08:00
金戟
f228ed5002 release v0.4.11 2021-02-02 07:49:20 +08:00
金戟
2303ac188e add roadmap 2021-02-01 23:49:16 +08:00
金戟
290d0db400 support specify source class for private accessor 2021-02-01 08:08:33 +08:00
金戟
77b955a5ce use system null type 2021-01-31 21:46:46 +08:00
金戟
ab8a0b32e5 fix compile time check for private member access 2021-01-31 20:18:53 +08:00
金戟
39c788cd02 check method parameter count 2021-01-31 15:10:34 +08:00
金戟
7029579534 fix intellij gradle build issue with incorrect file protocol syntax 2021-01-31 12:39:58 +08:00
金戟
ba951ce744 should always load array variable via aload 2021-01-30 23:53:22 +08:00
Fan Lin
2bbe2eba93 be more precise 2021-01-29 11:23:58 +08:00
Fan Lin
4c96f4cc0e Update use-mock.md
use static declaration for complicate mock method
2021-01-29 11:17:00 +08:00
Fan Lin
3a2e3032df Merge pull request #53 from npvip/patch-1
Update use-mock.md
2021-01-28 19:24:23 +08:00
npvip
530f3295bd Update use-mock.md
MOCK_CONTEXT.put不是set
2021-01-28 17:53:50 +08:00
金戟
650b385785 remove TestableMock annotation 2021-01-24 16:32:05 +08:00
金戟
99e7c3bf18 bump to version 0.4.10 2021-01-22 08:26:01 +08:00
金戟
ef5d21baf6 fit INVOKEDYNAMIC 2021-01-22 00:09:32 +08:00
金戟
f9be92e9d0 for InvocationTargetException, bring root cause out 2021-01-21 22:49:49 +08:00
金戟
019e407a4f Merge branch 'master' into 0.5
* master:
  support private method invoke with null parameter
  put parameter constructor doc to sidebar
  update en-us docs to fit targetClass parameter usage
2021-01-21 22:36:18 +08:00
金戟
6951b98c7e support private method invoke with null parameter 2021-01-21 22:03:53 +08:00
金戟
369c2bd192 put parameter constructor doc to sidebar 2021-01-19 15:34:02 +08:00
金戟
13de5e42ad update en-us docs to fit targetClass parameter usage 2021-01-13 17:45:34 +08:00
金戟
453908cd46 add NoVerify class for accessing private member of any object 2021-01-11 11:25:45 +08:00
金戟
2f110ae4d6 bump version to 0.4.9 2021-01-09 08:03:11 +08:00
金戟
7ba44ae618 validate member accessed via PrivateAccessor exists 2021-01-08 22:57:25 +08:00
金戟
66cb8f7732 correctly remove the invoker instance byte code 2021-01-08 17:30:30 +08:00
金戟
1f29aff00f manual recursively check argument is unnecessary 2021-01-08 11:29:27 +08:00
金戟
8d12bb42ba simplify description 2021-01-08 08:23:10 +08:00
金戟
c1744d2254 bump to v0.4.8 2021-01-08 08:14:12 +08:00
金戟
444967f676 recurisively looking into method parameters for more private access expression 2021-01-07 19:48:18 +08:00
金戟
860fdcf072 support private method invocation in assign statement 2021-01-07 18:55:33 +08:00
Fan Lin
851ae91406 Merge pull request #37 from Yiniu/master
解决:Windows下文件分隔符替换多重转义,URLCloassLoader路径不合规的问题,增加本地maven仓库
2021-01-07 14:02:33 +08:00
xuyuqing
580fed7dac 解决:Windows下文件分隔符替换多重转义的问题,增加本地maven仓库 2021-01-07 10:31:35 +08:00
金戟
f07ac8f21f always build with UTF-8 encoding 2021-01-06 16:31:05 +08:00
金戟
31a0b49b7a use targetClass in demo 2021-01-06 00:23:46 +08:00
金戟
0a1b46b352 fit for kotlin auto generated accessor method 2021-01-05 23:28:35 +08:00
金戟
55ec1ca5b5 follow up kotlin demo to same as java ones 2021-01-05 20:44:05 +08:00
金戟
2806d38c94 add shorten command line issue to faq 2021-01-05 15:51:22 +08:00
金戟
070aeb6559 fit mock invocation recorder for targetClass parameter 2021-01-05 14:57:20 +08:00
金戟
0255ffa911 record mock method desc 2021-01-05 14:15:18 +08:00
金戟
f64e0cd959 handle targetClass parameter 2021-01-05 10:34:05 +08:00
金戟
74b68d495a add targetClass parameter 2021-01-04 23:31:58 +08:00
金戟
6c0bd7de46 add code example for MOCK_CONTEXT in doc 2021-01-03 22:54:38 +08:00
金戟
593d134672 update docs 2021-01-03 21:44:16 +08:00
金戟
c496837861 add a place holder page for parameter constructor 2021-01-02 21:04:33 +08:00
金戟
b42ee1c9b9 also fit for EnhancerBySpringCGLIB 2021-01-02 19:47:12 +08:00
金戟
079410ee0f add about us docs 2021-01-02 16:23:57 +08:00
Fan Lin
acc3f0bdf2 Merge pull request #29 from itwild/master
fit for class which enhanced by CGLIB
2021-01-02 11:16:01 +08:00
金戟
6f9d535cee add future feature plan 2021-01-02 11:10:44 +08:00
itwild
631f45f4c5 fit for class which enhanced by CGLIB,for example,some class annotated with '@Service' in Spring Framework 2021-01-01 22:23:50 +08:00
金戟
94127064e8 add demo for private method without argument 2020-12-31 20:40:42 +08:00
金戟
36f170bdd3 release version 0.4.7 2020-12-31 07:30:21 +08:00
金戟
5ab1cf7a40 fit for gradle build in intellij 2020-12-30 23:51:38 +08:00
金戟
68075a2ead fix method node stack change 2020-12-30 22:38:17 +08:00
金戟
783b127bbb fit windows path split 2020-12-30 16:03:36 +08:00
金戟
cd7097ded9 release version 0.4.6 2020-12-30 07:24:49 +08:00
金戟
0f2c889c46 rename mock context class 2020-12-29 17:41:27 +08:00
金戟
df4ac8e950 for static invoke, no more stack required 2020-12-29 16:38:13 +08:00
金戟
9e393b5ef5 use mock context to inject extra mock parameters 2020-12-29 16:35:49 +08:00
金戟
bb47f5d199 raise a issue if any problem found 2020-12-29 13:23:18 +08:00
金戟
2efee1feb2 added notification of recent frequent questions 2020-12-29 13:18:06 +08:00
金戟
3df95db607 static constraint will be remove in future 2020-12-29 11:45:07 +08:00
金戟
a6b289e136 fix issue cause by iinc bytecode 2020-12-28 14:43:08 +08:00
金戟
3c375713aa update yunxiao link and doc description 2020-12-28 10:22:14 +08:00
金戟
9a7372d9ab recommand to use static declared mock method 2020-12-27 22:07:08 +08:00
金戟
e11f2e1645 release version 0.4.5 2020-12-27 20:13:07 +08:00
金戟
d14c570c96 search this reference properly 2020-12-27 13:11:26 +08:00
金戟
8a8065e179 show warning when access none static method in mock method 2020-12-27 11:35:14 +08:00
金戟
28bb6454c8 avoid source class be re-transformed 2020-12-27 11:34:25 +08:00
金戟
13fa8a76ce support dump transformed bytecode to local file 2020-12-26 21:48:57 +08:00
金戟
cbdab5a246 adapt source class loader to intellij context 2020-12-26 20:51:13 +08:00
金戟
d7945f52f3 extra global config 2020-12-26 19:31:10 +08:00
金戟
96ea5680df get Javac processing environment for intellij 2020-12-26 19:30:35 +08:00
金戟
ee21c8dcd8 transform mock method body to static context 2020-12-26 16:20:34 +08:00
金戟
281be6bd51 let mock method be static 2020-12-26 14:27:08 +08:00
金戟
a8b58ffe17 refer to lombok javac processing environment fetch 2020-12-26 11:48:36 +08:00
金戟
8d33cb718f test class package should match the class under test 2020-12-25 18:13:26 +08:00
金戟
2d8433056c jsr269 compile issue only effect in intellij 2020.2.2 version and above 2020-12-25 17:48:30 +08:00
金戟
32eefc25fe better translation 2020-12-24 22:38:17 +08:00
金戟
7f73fc0cc1 complete the english doc 2020-12-24 16:14:18 +08:00
金戟
44fed6ce8b add demo case for access private method with interface parameter 2020-12-24 11:58:17 +08:00
207 changed files with 7240 additions and 2039 deletions

View File

@@ -2,13 +2,27 @@
换种思路写Mock让单元测试更简单。
无需初始化,不挑测试框架甭管要换的是私有方法、静态方法、构造方法还是其他任何类的任何方法也甭管要换的对象是怎么创建的。写好Mock定义加个`@MockMethod`注解,一切统统搞定。
无需初始化,不挑服务框架甭管要换的是私有方法、静态方法、构造方法还是其他任何类的任何方法也甭管要换的对象是怎么创建的。写好Mock定义加个`@MockMethod`注解,一切统统搞定。
- 文档https://alibaba.github.io/testable-mock/
- 国内文档镜像http://freyrlin.gitee.io/testable-mock/ (速度快,内容稍有延迟)
- 国内文档镜像http://freyrlin.gitee.io/testable-mock/
阅读[这里](https://mp.weixin.qq.com/s/KyU6Eu7mDkZU8FspfSqfMw)了解更多故事。
<font size="5">**0.6版本已发布**</font>,从`0.5.x`升级到`0.6.x`版本请参考[版本升级指南](https://alibaba.github.io/testable-mock/#/zh-cn/doc/upgrade-guide)
如果有遇到其他任何使用问题和建议,请直接在[Issue](https://github.com/alibaba/testable-mock/issues)中提出,也可通过[Pull Request](https://github.com/alibaba/testable-mock/pulls)提交您的代码我们将在24小时内回复并处理
-----
## 版本计划
`TestableMock`正在持续迭代演进,以下版本计划可能在开发过程中发生调整
- `0.6` 当前版本,进行中的工作内容参考[Issue](https://github.com/alibaba/testable-mock/issues)清单
- `0.7` 完善Mock功能的防代码重构机制支持快速Mock指定类型的所有方法
- `1.0` 功能稳定,一个崭新的开始
## 目录结构
```bash

View File

@@ -7,6 +7,14 @@ Write a mock method, add an `@MockMethod` annotation, everything is done.
Usage Document: https://alibaba.github.io/testable-mock/#/en-us/
## Roadmap
`TestableMock` is still under heavy development, the following version plans may be adjusted during the iteration
- `v0.6` it's the current version, refer to the [issue](https://github.com/alibaba/testable-mock/issues) list for the work in progress
- `v0.7` better anti-refactor support of mocking, allow quick mock all method in specified class
- `v1.0` all functions are stable, a brand-new start
## Directory Structure
```bash
@@ -14,7 +22,7 @@ Usage Document: https://alibaba.github.io/testable-mock/#/en-us/
|-- testable-all ➜ Dependence aggration, for easily import all modules at once
|-- testable-processor ➜ Compile-time code preprocessing module, provides test assist functions
|-- testable-agent ➜ JavaAgent module, provides test mocking related functions
|-- testable-core ➜ Basic function module, provides Mock related class and annotation
|-- testable-core ➜ Basic function module, provides mock related class and annotation
|-- testable-maven-plugin ➜ Maven plugin module, for simplify JavaAgent injection
|-- demo
| |-- java-demo ➜ Java code example

View File

@@ -7,13 +7,18 @@ version = '1.0.0-SNAPSHOT'
sourceCompatibility = '8'
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
testImplementation('com.alibaba.testable:testable-all:0.4.4')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.4.4')
testImplementation('com.alibaba.testable:testable-all:0.6.0')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.6.0')
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
test {

View File

@@ -12,7 +12,7 @@
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>5.6.2</junit.version>
<testable.version>0.4.4</testable.version>
<testable.version>0.6.0</testable.version>
</properties>
<dependencies>
@@ -32,15 +32,14 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
</configuration>
</plugin>
<configuration>
<argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
</configuration>
</plugin>
<!-- <plugin>-->
<!-- <groupId>com.alibaba.testable</groupId>-->
<!-- <artifactId>testable-maven-plugin</artifactId>-->

View File

@@ -1,8 +1,8 @@
package com.alibaba.testable.demo;
package com.alibaba.demo.basic;
import com.alibaba.testable.demo.model.BlackBox;
import com.alibaba.testable.demo.model.Box;
import com.alibaba.testable.demo.model.Color;
import com.alibaba.demo.basic.model.mock.BlackBox;
import com.alibaba.demo.basic.model.mock.Box;
import com.alibaba.demo.basic.model.mock.Color;
/**
* 演示父类变量引用子类对象时的Mock场景

View File

@@ -0,0 +1,60 @@
package com.alibaba.demo.basic;
import java.util.concurrent.*;
/**
* 演示对内部类的Mock支持
* Demonstrate support for mocking invocation inside a inner class
*/
public class DemoInnerClass {
public static class StaticInner {
/**
* invocation inside a static inner class
*/
public String demo() {
return methodToBeMock();
}
}
public class Inner {
/**
* invocation inside a non-static inner class
*/
public String demo() {
return methodToBeMock();
}
}
private ExecutorService executorService = Executors.newSingleThreadExecutor();
public String callAnonymousInner() throws ExecutionException, InterruptedException {
/**
* invocation inside a anonymous inner class
*/
Future<String> future = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return methodToBeMock();
}
});
return future.get();
}
public String callLambdaInner() throws ExecutionException, InterruptedException {
/**
* invocation inside a lambda inner class
*/
Future<String> future = executorService.submit(() -> methodToBeMock());
return future.get();
}
public String callInnerDemo() {
return new Inner().demo();
}
public static String methodToBeMock() {
return "RealCall";
}
}

View File

@@ -1,7 +1,7 @@
package com.alibaba.testable.demo;
package com.alibaba.demo.basic;
import com.alibaba.testable.demo.model.BlackBox;
import com.alibaba.demo.basic.model.mock.BlackBox;
import java.util.ArrayList;
import java.util.HashMap;

View File

@@ -1,6 +1,6 @@
package com.alibaba.testable.demo;
package com.alibaba.demo.basic;
import com.alibaba.testable.demo.model.BlackBox;
import com.alibaba.demo.basic.model.mock.BlackBox;
import java.nio.file.Files;
import java.nio.file.Paths;

View File

@@ -0,0 +1,52 @@
package com.alibaba.demo.basic;
import java.util.List;
/**
* 演示私有成员访问功能
* Demonstrate private member access functionality
*/
public class DemoPrivateAccess {
/**
* a private static field
*/
private static int staticCount;
/**
* a private member field
*/
private int count;
/**
* a constant field
*/
public final Double pi = 3.14;
/**
* private static method without arguments
*/
private static String privateStaticFunc() {
return "static";
}
/**
* private static method with arguments
*/
private static String privateStaticFuncWithArgs(String str, int i) {
return (str == null ? "null" : str) + " + " + i;
}
/**
* private member method without arguments
*/
private String privateFunc() {
return "member";
}
/**
* private member method with arguments
*/
private String privateFuncWithArgs(List<String> list, String str, int i) {
return list.stream().reduce((a, s) -> a + s).orElse("") + " + " + str + " + " + i;
}
}

View File

@@ -1,4 +1,4 @@
package com.alibaba.testable.demo;
package com.alibaba.demo.basic;
import java.util.*;

View File

@@ -1,4 +1,4 @@
package com.alibaba.testable.demo.model;
package com.alibaba.demo.basic.model.mock;
public class BlackBox extends Box implements Color {

View File

@@ -1,4 +1,4 @@
package com.alibaba.testable.demo.model;
package com.alibaba.demo.basic.model.mock;
abstract public class Box {

View File

@@ -1,4 +1,4 @@
package com.alibaba.testable.demo.model;
package com.alibaba.demo.basic.model.mock;
public interface Color {

View File

@@ -0,0 +1,47 @@
package com.alibaba.demo.basic.model.omni;
public class Child {
/**
* 我是一个私有的构造方法
* This class have only private constructor
*/
private Child() {
}
// ---------- 内部成员字段 ----------
private GrandChild grandChild;
private InnerChild subChild;
/* ---------------------------------------
以下Getters/Setters方法仅为便于功能演示而添加
并非OmniConstructor或OmniAccessor功能所需
--------------------------------------- */
public GrandChild getGrandChild() {
return grandChild;
}
public void setGrandChild(GrandChild grandChild) {
this.grandChild = grandChild;
}
public InnerChild getSubChild() {
return subChild;
}
public void setSubChild(InnerChild subChild) {
this.subChild = subChild;
}
/**
* 这是一个私有内部类
* An private inner class
*/
private class InnerChild {
private String secret;
}
}

View File

@@ -0,0 +1,31 @@
package com.alibaba.demo.basic.model.omni;
public class GrandChild {
// ---------- 内部成员字段 ----------
private int value;
private String content;
/* ---------------------------------------
以下Getters/Setters方法仅为便于功能演示而添加
并非OmniConstructor或OmniAccessor功能所需
--------------------------------------- */
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}

View File

@@ -0,0 +1,40 @@
package com.alibaba.demo.basic.model.omni;
public class Parent {
/**
* 我是一个虽然存在但无法正常使用的构造方法
* This class have constructor with exception throw
*/
public Parent() {
throw new IllegalArgumentException();
}
// ---------- 内部成员字段 ----------
private Child child;
private Child[] children;
/* ---------------------------------------
以下Getters/Setters方法仅为便于功能演示而添加
并非OmniConstructor或OmniAccessor功能所需
--------------------------------------- */
public Child getChild() {
return child;
}
public void setChild(Child child) {
this.child = child;
}
public Child[] getChildren() {
return children;
}
public void setChildren(Child[] children) {
this.children = children;
}
}

View File

@@ -0,0 +1,9 @@
package com.alibaba.demo.one2multi;
public class ASvc {
public String demo(String name) {
return String.format("a_%s", name);
}
}

View File

@@ -0,0 +1,9 @@
package com.alibaba.demo.one2multi;
public class BSvc {
public String demo(String name) {
return String.format("b_%s", name);
}
}

View File

@@ -0,0 +1,9 @@
package com.alibaba.demo.one2multi;
public class CSvc {
public String demo(String name) {
return String.format("c_%s", name);
}
}

View File

@@ -1,36 +0,0 @@
package com.alibaba.testable.demo;
/**
* 演示私有成员访问功能
* Demonstrate private member access functionality
*/
public class DemoPrivateAccess {
/**
* a private static field
*/
private static int staticCount;
/**
* a private member field
*/
private int count;
/**
* a constant field
*/
public final Double pi = 3.14;
/**
* private static method
*/
private static String privateStaticFunc(String s, int i) {
return s + " + " + i;
}
/**
* private member method
*/
private String privateFunc(String s, int i) {
return s + " - " + i;
}
}

View File

@@ -1,9 +1,9 @@
package com.alibaba.testable.demo;
package com.alibaba.demo.basic;
import com.alibaba.demo.basic.model.mock.BlackBox;
import com.alibaba.demo.basic.model.mock.Box;
import com.alibaba.demo.basic.model.mock.Color;
import com.alibaba.testable.core.annotation.MockMethod;
import com.alibaba.testable.demo.model.BlackBox;
import com.alibaba.testable.demo.model.Box;
import com.alibaba.testable.demo.model.Color;
import org.junit.jupiter.api.Test;
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
@@ -17,74 +17,75 @@ class DemoInheritTest {
private DemoInherit demoInherit = new DemoInherit();
@MockMethod(targetMethod = "put")
private void put_into_box(Box self, String something) {
self.put("put_" + something + "_into_box");
}
public static class Mock {
@MockMethod(targetMethod = "put")
private void put_into_box(Box self, String something) {
self.put("put_" + something + "_into_box");
}
@MockMethod(targetMethod = "put")
private void put_into_blackbox(BlackBox self, String something) {
self.put("put_" + something + "_into_blackbox");
}
@MockMethod(targetMethod = "put")
private void put_into_blackbox(BlackBox self, String something) {
self.put("put_" + something + "_into_blackbox");
}
@MockMethod(targetMethod = "get")
private String get_from_box(Box self) {
return "get_from_box";
}
@MockMethod(targetMethod = "get")
private String get_from_box(Box self) {
return "get_from_box";
}
@MockMethod(targetMethod = "get")
private String get_from_blackbox(BlackBox self) {
return "get_from_blackbox";
}
@MockMethod(targetMethod = "get")
private String get_from_blackbox(BlackBox self) {
return "get_from_blackbox";
}
@MockMethod(targetMethod = "getColor")
private String get_color_from_color(Color self) {
return "color_from_color";
}
@MockMethod(targetMethod = "getColor")
private String get_color_from_color(Color self) {
return "color_from_color";
}
@MockMethod(targetMethod = "getColor")
private String get_color_from_blackbox(BlackBox self) {
@MockMethod(targetMethod = "getColor")
private String get_color_from_blackbox(BlackBox self) {
return "color_from_blackbox";
}
}
@Test
void should_able_to_mock_call_sub_object_method_by_parent_object() {
void should_mock_call_sub_object_method_by_parent_object() {
BlackBox box = (BlackBox)demoInherit.putIntoBox();
verify("put_into_box").withTimes(1);
assertEquals("put_data_into_box", box.get());
}
@Test
void should_able_to_mock_call_sub_object_method_by_sub_object() {
void should_mock_call_sub_object_method_by_sub_object() {
BlackBox box = demoInherit.putIntoBlackBox();
verify("put_into_blackbox").withTimes(1);
assertEquals("put_data_into_blackbox", box.get());
}
@Test
void should_able_to_mock_call_parent_object_method_by_parent_object() {
void should_mock_call_parent_object_method_by_parent_object() {
String content = demoInherit.getFromBox();
verify("get_from_box").withTimes(1);
assertEquals("get_from_box", content);
}
@Test
void should_able_to_mock_call_parent_object_method_by_sub_object() {
void should_mock_call_parent_object_method_by_sub_object() {
String content = demoInherit.getFromBlackBox();
verify("get_from_blackbox").withTimes(1);
assertEquals("get_from_blackbox", content);
}
@Test
void should_able_to_mock_call_interface_method_by_interface_object() {
void should_mock_call_interface_method_by_interface_object() {
String color = demoInherit.getColorViaColor();
verify("get_color_from_color").withTimes(1);
assertEquals("color_from_color", color);
}
@Test
void should_able_to_mock_call_interface_method_by_sub_class_object() {
void should_mock_call_interface_method_by_sub_class_object() {
String color = demoInherit.getColorViaBox();
verify("get_color_from_blackbox").withTimes(1);
assertEquals("color_from_blackbox", color);

View File

@@ -0,0 +1,29 @@
package com.alibaba.demo.basic;
import com.alibaba.testable.core.annotation.MockMethod;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* 演示对内部类的Mock支持
* Demonstrate support for mocking invocation inside a inner class
*/
class DemoInnerClassTest {
public static class Mock {
@MockMethod(targetClass = DemoInnerClass.class)
String methodToBeMock() {
return "MockedCall";
}
}
@Test
void should_mock_invoke_inside_inner_class() throws Exception {
DemoInnerClass demo = new DemoInnerClass();
assertEquals("MockedCall", demo.callInnerDemo());
assertEquals("MockedCall", demo.callAnonymousInner());
assertEquals("MockedCall", demo.callLambdaInner());
assertEquals("MockedCall", new DemoInnerClass.StaticInner().demo());
}
}

View File

@@ -1,8 +1,8 @@
package com.alibaba.testable.demo;
package com.alibaba.demo.basic;
import com.alibaba.demo.basic.model.mock.BlackBox;
import com.alibaba.testable.core.annotation.MockMethod;
import com.alibaba.testable.core.error.VerifyFailedError;
import com.alibaba.testable.demo.model.BlackBox;
import org.junit.jupiter.api.Test;
import static com.alibaba.testable.core.matcher.InvokeMatcher.*;
@@ -17,15 +17,16 @@ class DemoMatcherTest {
private DemoMatcher demoMatcher = new DemoMatcher();
@MockMethod(targetMethod = "methodToBeMocked")
private void methodWithoutArgument(DemoMatcher self) {}
public static class Mock {
@MockMethod(targetMethod = "methodToBeMocked")
private void methodWithoutArgument(DemoMatcher self) {}
@MockMethod(targetMethod = "methodToBeMocked")
private void methodWithArguments(DemoMatcher self, Object a1, Object a2) {}
@MockMethod(targetMethod = "methodToBeMocked")
private void methodWithArrayArgument(DemoMatcher self, Object[] a) {}
@MockMethod(targetMethod = "methodToBeMocked")
private void methodWithArguments(DemoMatcher self, Object a1, Object a2) {}
@MockMethod(targetMethod = "methodToBeMocked")
private void methodWithArrayArgument(DemoMatcher self, Object[] a) {}
}
@Test
void should_match_no_argument() {

View File

@@ -0,0 +1,121 @@
package com.alibaba.demo.basic;
import com.alibaba.demo.basic.model.mock.BlackBox;
import com.alibaba.testable.core.annotation.MockConstructor;
import com.alibaba.testable.core.annotation.MockMethod;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.concurrent.Executors;
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
import static com.alibaba.testable.core.tool.TestableTool.MOCK_CONTEXT;
import static com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* 演示基本的Mock功能
* Demonstrate basic mock functionality
*/
class DemoMockTest {
private DemoMock demoMock = new DemoMock();
public static class Mock {
@MockConstructor
private BlackBox createBlackBox(String text) {
return new BlackBox("mock_" + text);
}
@MockMethod(targetClass = DemoMock.class)
private String innerFunc(String text) {
return "mock_" + text;
}
@MockMethod(targetClass = DemoMock.class)
private String staticFunc() {
return "_MOCK_TAIL";
}
@MockMethod(targetClass = String.class)
private String trim() {
return "trim_string";
}
@MockMethod(targetClass = String.class, targetMethod = "substring")
private String sub(int i, int j) {
return "sub_string";
}
@MockMethod(targetClass = String.class)
private boolean startsWith(String s) {
return false;
}
@MockMethod(targetClass = BlackBox.class)
private BlackBox secretBox() {
return new BlackBox("not_secret_box");
}
@MockMethod(targetClass = DemoMock.class)
private String callFromDifferentMethod() {
if ("special_case".equals(MOCK_CONTEXT.get("case"))) {
return "mock_special";
}
switch (SOURCE_METHOD) {
case "callerOne":
return "mock_one";
default:
return "mock_others";
}
}
}
@Test
void should_mock_new_object() {
assertEquals("mock_something", demoMock.newFunc());
verify("createBlackBox").with("something");
}
@Test
void should_mock_member_method() throws Exception {
assertEquals("{ \"res\": \"mock_hello_MOCK_TAIL\"}", demoMock.outerFunc("hello"));
verify("innerFunc").with("hello");
verify("staticFunc").with();
}
@Test
void should_mock_common_method() {
assertEquals("trim_string__sub_string__false", demoMock.commonFunc());
verify("trim").withTimes(1);
verify("sub").withTimes(1);
verify("startsWith").withTimes(1);
}
@Test
void should_mock_static_method() {
Assertions.assertEquals("not_secret_box", demoMock.getBox().get());
verify("secretBox").withTimes(1);
}
@Test
void should_get_source_method_name() throws Exception {
// synchronous
assertEquals("mock_one_mock_others", demoMock.callerOne() + "_" + demoMock.callerTwo());
// asynchronous
assertEquals("mock_one_mock_others",
Executors.newSingleThreadExecutor().submit(() -> demoMock.callerOne() + "_" + demoMock.callerTwo()).get());
verify("callFromDifferentMethod").withTimes(4);
}
@Test
void should_set_mock_context() throws Exception {
MOCK_CONTEXT.put("case", "special_case");
// synchronous
assertEquals("mock_special", demoMock.callerOne());
// asynchronous
assertEquals("mock_special", Executors.newSingleThreadExecutor().submit(() -> demoMock.callerOne()).get());
verify("callFromDifferentMethod").withTimes(2);
}
}

View File

@@ -0,0 +1,91 @@
package com.alibaba.demo.basic;
import com.alibaba.demo.basic.model.omni.Child;
import com.alibaba.demo.basic.model.omni.Parent;
import com.alibaba.testable.core.tool.OmniAccessor;
import com.alibaba.testable.core.tool.OmniConstructor;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* 演示快速创建任意对象和使用路径访问成员
* Demonstrate quick object construction and access members by path
*/
class DemoOmniMethodsTest {
@Test
void should_construct_any_class() {
Parent parent = OmniConstructor.newInstance(Parent.class);
// 任意深度的子孙成员对象都不为空
assertNotNull(parent.getChild().getGrandChild().getContent());
// 所有基础类型初始化为默认数值
assertEquals(0, parent.getChild().getGrandChild().getValue());
assertEquals("", parent.getChild().getGrandChild().getContent());
// 所有数组类型初始化为空数组
assertEquals(0, parent.getChildren().length);
}
@Test
void should_get_any_member() {
Parent parent = OmniConstructor.newInstance(Parent.class);
parent.setChildren(OmniConstructor.newArray(Child.class, 3));
parent.getChild().getGrandChild().setContent("from child");
parent.getChildren()[0].getGrandChild().setContent("from 1st children");
parent.getChildren()[1].getGrandChild().setContent("from 2nd children");
parent.getChildren()[2].getGrandChild().setContent("from 3rd children");
// 使用成员名称快速读取成员对象
List<String> contents = OmniAccessor.get(parent, "content");
assertEquals(4, contents.size());
assertEquals("from child", contents.get(0));
assertEquals("from 1st children", contents.get(1));
assertEquals("from 2nd children", contents.get(2));
assertEquals("from 3rd children", contents.get(3));
// 使用成员类型快速读取成员对象
contents = OmniAccessor.get(parent, "{Child}/{GrandChild}/content");
assertEquals(1, contents.size());
assertEquals("from child", contents.get(0));
// 使用带下标的路径读取成员对象
assertEquals("from 2nd children", OmniAccessor.getFirst(parent, "children[1]/{GrandChild}/content"));
assertEquals("from 3rd children", OmniAccessor.getFirst(parent, "{Child[]}[2]/{GrandChild}/content"));
// 使用模糊路径快速读取成员对象
assertEquals("from 1st children", OmniAccessor.getFirst(parent, "{C*[]}[0]/*/con*t"));
}
@Test
void should_set_any_member() {
Parent parent = OmniConstructor.newInstance(Parent.class);
parent.setChildren(OmniConstructor.newArray(Child.class, 3));
// 使用指定路径快速给成员对象赋值
OmniAccessor.set(parent, "child/grandChild/content", "demo child");
assertEquals("demo child", parent.getChild().getGrandChild().getContent());
// 使用带下标的路径给成员对象赋值
OmniAccessor.set(parent, "children[1]/grandChild/content", "demo children[1]");
assertEquals("demo children[1]", parent.getChildren()[1].getGrandChild().getContent());
// 使用模糊路径批量给成员对象赋值
OmniAccessor.set(parent, "child*/*/content", "demo in batch");
assertEquals("demo in batch", parent.getChild().getGrandChild().getContent());
assertEquals("demo in batch", parent.getChildren()[0].getGrandChild().getContent());
assertEquals("demo in batch", parent.getChildren()[1].getGrandChild().getContent());
assertEquals("demo in batch", parent.getChildren()[2].getGrandChild().getContent());
// 读写私有内部类类型的成员(使用类型名引用内部类时,无需带外部类名)
assertEquals("", OmniAccessor.getFirst(parent, "subChild/secret"));
OmniAccessor.set(parent, "{InnerChild}/secret", "inner-class secret");
assertEquals("inner-class secret", OmniAccessor.getFirst(parent, "subChild/secret"));
}
}

View File

@@ -0,0 +1,58 @@
package com.alibaba.demo.basic;
import com.alibaba.testable.core.tool.PrivateAccessor;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* 演示使用`PrivateAccessor`工具类访问私有成员
* Demonstrate access private member via `PrivateAccessor` class
*/
class DemoPrivateAccessorTest {
private DemoPrivateAccess demoPrivateAccess = new DemoPrivateAccess();
@Test
void should_access_private_method() {
List<String> list = new ArrayList<String>() {{ add("a"); add("b"); add("c"); }};
assertEquals("member", PrivateAccessor.invoke(demoPrivateAccess, "privateFunc"));
assertEquals("abc + hello + 1", PrivateAccessor.invoke(demoPrivateAccess, "privateFuncWithArgs", list, "hello", 1));
}
@Test
void should_access_private_field() {
PrivateAccessor.set(demoPrivateAccess, "count", 3);
assertEquals(Integer.valueOf(3), PrivateAccessor.get(demoPrivateAccess, "count"));
}
@Test
void should_access_private_static_method() {
assertEquals("static", PrivateAccessor.invokeStatic(DemoPrivateAccess.class, "privateStaticFunc"));
assertEquals("hello + 1", PrivateAccessor.invokeStatic(DemoPrivateAccess.class, "privateStaticFuncWithArgs", "hello", 1));
}
@Test
void should_access_private_static_field() {
PrivateAccessor.setStatic(DemoPrivateAccess.class, "staticCount", 3);
assertEquals(Integer.valueOf(3), PrivateAccessor.getStatic(DemoPrivateAccess.class, "staticCount"));
}
@Test
void should_update_final_field() {
PrivateAccessor.set(demoPrivateAccess, "pi", 3.14);
assertEquals(Double.valueOf(3.14), PrivateAccessor.get(demoPrivateAccess, "pi"));
}
@Test
void should_use_null_parameter() {
PrivateAccessor.set(demoPrivateAccess, "pi", null);
assertNull(PrivateAccessor.get(demoPrivateAccess, "pi"));
assertEquals("null + 1", PrivateAccessor.invokeStatic(DemoPrivateAccess.class, "privateStaticFuncWithArgs", null, 1));
}
}

View File

@@ -0,0 +1,59 @@
package com.alibaba.demo.basic;
import com.alibaba.testable.processor.annotation.EnablePrivateAccess;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* 演示使用`@EnablePrivateAccess`注解访问私有成员
* Demonstrate access private member via `@EnablePrivateAccess` annotation
*/
@EnablePrivateAccess(srcClass = DemoPrivateAccess.class)
class DemoPrivateProcessorTest {
private DemoPrivateAccess demoPrivateAccess = new DemoPrivateAccess();
@Test
void should_access_private_method() {
List<String> list = new ArrayList<String>() {{ add("a"); add("b"); add("c"); }};
assertEquals("member", demoPrivateAccess.privateFunc());
assertEquals("abc + hello + 1", demoPrivateAccess.privateFuncWithArgs(list, "hello", 1));
}
@Test
void should_access_private_field() {
demoPrivateAccess.count = 2;
assertEquals(Integer.valueOf(2), demoPrivateAccess.count);
}
@Test
void should_access_private_static_method() {
assertEquals("static", DemoPrivateAccess.privateStaticFunc());
assertEquals("hello + 1", DemoPrivateAccess.privateStaticFuncWithArgs("hello", 1));
}
@Test
void should_access_private_static_field() {
DemoPrivateAccess.staticCount = 2;
assertEquals(Integer.valueOf(2), DemoPrivateAccess.staticCount);
}
@Test
void should_update_final_field() {
demoPrivateAccess.pi = 4.13;
assertEquals(Double.valueOf(4.13), demoPrivateAccess.pi);
}
@Test
void should_use_null_parameter() {
demoPrivateAccess.pi = null;
assertNull(demoPrivateAccess.pi);
assertEquals("null + 1", DemoPrivateAccess.privateStaticFuncWithArgs(null, 1));
}
}

View File

@@ -0,0 +1,95 @@
package com.alibaba.demo.basic;
import com.alibaba.testable.core.annotation.MockConstructor;
import com.alibaba.testable.core.annotation.MockMethod;
import com.alibaba.demo.basic.DemoTemplate;
import org.junit.jupiter.api.Test;
import java.util.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* 演示模板方法的Mock场景
* Demonstrate scenario of mocking template method
*/
class DemoTemplateTest {
private DemoTemplate demoTemplate = new DemoTemplate();
public static class Mock {
/* 第一种写法:使用泛型定义 */
/* First solution: use generics type */
@MockMethod
private <T> List<T> getList(DemoTemplate self, T value) {
return new ArrayList<T>() {{ add((T)(value.toString() + "_mock_list")); }};
}
@MockMethod
private <K, V> Map<K, V> getMap(DemoTemplate self, K key, V value) {
return new HashMap<K, V>() {{ put(key, (V)(value.toString() + "_mock_map")); }};
}
@MockConstructor
private <T> HashSet<T> newHashSet() {
HashSet<T> set = new HashSet<>();
set.add((T)"insert_mock");
return set;
}
@MockMethod
private <E> boolean add(Set s, E e) {
s.add(e.toString() + "_mocked");
return true;
}
/* 第二种写法使用Object类型 */
/* Second solution: use object type */
//@MockMethod
//private List<Object> getList(DemoTemplate self, Object value) {
// return new ArrayList<Object>() {{ add(value.toString() + "_mock_list"); }};
//}
//
//@MockMethod
//private Map<Object, Object> getMap(DemoTemplate self, Object key, Object value) {
// return new HashMap<Object, Object>() {{ put(key, value.toString() + "_mock_map"); }};
//}
//
//@MockConstructor
//private HashSet newHashSet() {
// HashSet<Object> set = new HashSet<>();
// set.add("insert_mock");
// return set;
//}
//
//@MockMethod
//private boolean add(Set s, Object e) {
// s.add(e.toString() + "_mocked");
// return true;
//}
}
@Test
void should_mock_single_template_method() {
String res = demoTemplate.singleTemplateMethod();
assertEquals("demo_mock_list", res);
}
@Test
void should_mock_double_template_method() {
String res = demoTemplate.doubleTemplateMethod();
assertEquals("testable_mock_map", res);
}
@Test
void should_mock_new_template_method() {
Set<?> res = demoTemplate.newTemplateMethod();
assertEquals(2, res.size());
Iterator<?> iterator = res.stream().iterator();
assertEquals("insert_mock", iterator.next());
assertEquals("world_mocked", iterator.next());
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,27 @@
package com.alibaba.demo.one2multi;
import com.alibaba.testable.core.annotation.MockWith;
import org.junit.jupiter.api.Test;
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
import static org.junit.jupiter.api.Assertions.assertEquals;
@MockWith
public class OneToMultiSvcTest {
private ASvc aSvc = new ASvc();
private BSvc bSvc = new BSvc();
private CSvc cSvc = new CSvc();
@Test
public void should_test_multi_class_together() {
assertEquals("a_mock", aSvc.demo("test"));
assertEquals("b_mock", bSvc.demo("test"));
assertEquals("c_mock", cSvc.demo("test"));
verify("a_format").withTimes(1);
verify("b_format").withTimes(1);
verify("c_format").withTimes(1);
}
}

View File

@@ -1,115 +0,0 @@
package com.alibaba.testable.demo;
import com.alibaba.testable.core.annotation.MockConstructor;
import com.alibaba.testable.core.annotation.MockMethod;
import com.alibaba.testable.demo.model.BlackBox;
import org.junit.jupiter.api.Test;
import java.util.concurrent.Executors;
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
import static com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD;
import static com.alibaba.testable.core.tool.TestableTool.TEST_CASE;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* 演示基本的Mock功能
* Demonstrate basic mock functionality
*/
class DemoMockTest {
private DemoMock demoMock = new DemoMock();
@MockConstructor
private BlackBox createBlackBox(String text) {
return new BlackBox("mock_" + text);
}
@MockMethod
private String innerFunc(DemoMock self, String text) {
return "mock_" + text;
}
@MockMethod
private String staticFunc(DemoMock self) {
return "_MOCK_TAIL";
}
@MockMethod
private String trim(String self) {
return "trim_string";
}
@MockMethod(targetMethod = "substring")
private String sub(String self, int i, int j) {
return "sub_string";
}
@MockMethod
private boolean startsWith(String self, String s) {
return false;
}
@MockMethod
private BlackBox secretBox(BlackBox ignore) {
return new BlackBox("not_secret_box");
}
@MockMethod
private String callFromDifferentMethod(DemoMock self) {
if (TEST_CASE.equals("should_able_to_get_test_case_name")) {
return "mock_special";
}
switch (SOURCE_METHOD) {
case "callerOne": return "mock_one";
default: return "mock_others";
}
}
@Test
void should_able_to_mock_new_object() {
assertEquals("mock_something", demoMock.newFunc());
verify("createBlackBox").with("something");
}
@Test
void should_able_to_mock_member_method() throws Exception {
assertEquals("{ \"res\": \"mock_hello_MOCK_TAIL\"}", demoMock.outerFunc("hello"));
verify("innerFunc").with("hello");
}
@Test
void should_able_to_mock_common_method() {
assertEquals("trim_string__sub_string__false", demoMock.commonFunc());
verify("trim").withTimes(1);
verify("sub").withTimes(1);
verify("startsWith").withTimes(1);
}
@Test
void should_able_to_mock_static_method() {
assertEquals("not_secret_box", demoMock.getBox().get());
verify("secretBox").withTimes(1);
}
@Test
void should_able_to_get_source_method_name() throws Exception {
// synchronous
assertEquals("mock_one_mock_others", demoMock.callerOne() + "_" + demoMock.callerTwo());
// asynchronous
assertEquals("mock_one_mock_others",
Executors.newSingleThreadExecutor().submit(() -> demoMock.callerOne() + "_" + demoMock.callerTwo()).get());
verify("callFromDifferentMethod").withTimes(4);
}
@Test
void should_able_to_get_test_case_name() throws Exception {
// synchronous
assertEquals("mock_special", demoMock.callerOne());
// asynchronous
assertEquals("mock_special", Executors.newSingleThreadExecutor().submit(() -> demoMock.callerOne()).get());
verify("callFromDifferentMethod").withTimes(2);
}
}

View File

@@ -1,54 +0,0 @@
package com.alibaba.testable.demo;
import com.alibaba.testable.core.accessor.PrivateAccessor;
import com.alibaba.testable.processor.annotation.EnablePrivateAccess;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* 演示私有成员访问功能
* Demonstrate private member access functionality
*/
@EnablePrivateAccess
class DemoPrivateAccessTest {
private DemoPrivateAccess demoPrivateAccess = new DemoPrivateAccess();
@Test
void should_able_to_access_private_method() {
assertEquals("hello - 1", demoPrivateAccess.privateFunc("hello", 1));
assertEquals("hello - 1", PrivateAccessor.invoke(demoPrivateAccess, "privateFunc", "hello", 1));
}
@Test
void should_able_to_access_private_field() {
demoPrivateAccess.count = 2;
assertEquals(new Integer(2), demoPrivateAccess.count);
PrivateAccessor.set(demoPrivateAccess, "count", 3);
assertEquals(new Integer(3), PrivateAccessor.get(demoPrivateAccess, "count"));
}
@Test
void should_able_to_access_private_static_method() {
assertEquals("hello + 1", DemoPrivateAccess.privateStaticFunc("hello", 1));
assertEquals("hello + 1", PrivateAccessor.invokeStatic(DemoPrivateAccess.class, "privateStaticFunc", "hello", 1));
}
@Test
void should_able_to_access_private_static_field() {
DemoPrivateAccess.staticCount = 2;
assertEquals(new Integer(2), DemoPrivateAccess.staticCount);
PrivateAccessor.setStatic(DemoPrivateAccess.class, "staticCount", 3);
assertEquals(new Integer(3), PrivateAccessor.getStatic(DemoPrivateAccess.class, "staticCount"));
}
@Test
void should_able_to_update_final_field() {
demoPrivateAccess.pi = 4.13;
assertEquals(4.13, demoPrivateAccess.pi);
}
}

View File

@@ -1,93 +0,0 @@
package com.alibaba.testable.demo;
import com.alibaba.testable.core.annotation.MockConstructor;
import com.alibaba.testable.core.annotation.MockMethod;
import org.junit.jupiter.api.Test;
import java.util.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* 演示模板方法的Mock场景
* Demonstrate scenario of mocking template method
*/
class DemoTemplateTest {
private DemoTemplate demoTemplate = new DemoTemplate();
/* 第一种写法:使用泛型定义 */
/* First solution: use generics type */
@MockMethod
private <T> List<T> getList(DemoTemplate self, T value) {
return new ArrayList<T>() {{ add((T)(value.toString() + "_mock_list")); }};
}
@MockMethod
private <K, V> Map<K, V> getMap(DemoTemplate self, K key, V value) {
return new HashMap<K, V>() {{ put(key, (V)(value.toString() + "_mock_map")); }};
}
@MockConstructor
private <T> HashSet<T> newHashSet() {
HashSet<T> set = new HashSet<>();
set.add((T)"insert_mock");
return set;
}
@MockMethod
private <E> boolean add(Set s, E e) {
s.add(e.toString() + "_mocked");
return true;
}
/* 第二种写法使用Object类型 */
/* Second solution: use object type */
//@MockMethod
//private List<Object> getList(DemoTemplate self, Object value) {
// return new ArrayList<Object>() {{ add(value.toString() + "_mock_list"); }};
//}
//
//@MockMethod
//private Map<Object, Object> getMap(DemoTemplate self, Object key, Object value) {
// return new HashMap<Object, Object>() {{ put(key, value.toString() + "_mock_map"); }};
//}
//
//@MockConstructor
//private HashSet newHashSet() {
// HashSet<Object> set = new HashSet<>();
// set.add("insert_mock");
// return set;
//}
//
//@MockMethod
//private boolean add(Set s, Object e) {
// s.add(e.toString() + "_mocked");
// return true;
//}
@Test
void should_able_to_mock_single_template_method() {
String res = demoTemplate.singleTemplateMethod();
assertEquals("demo_mock_list", res);
}
@Test
void should_able_to_mock_double_template_method() {
String res = demoTemplate.doubleTemplateMethod();
assertEquals("testable_mock_map", res);
}
@Test
void should_able_to_mock_new_template_method() {
Set<?> res = demoTemplate.newTemplateMethod();
assertEquals(2, res.size());
Iterator<?> iterator = res.stream().iterator();
assertEquals("insert_mock", iterator.next());
assertEquals("world_mocked", iterator.next());
}
}

View File

@@ -1,7 +1,7 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.4.10"
kotlin("jvm") version "1.4.10"
}
group = "com.alibaba.testable"
@@ -9,25 +9,25 @@ version = "1.0.0-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_1_8
repositories {
mavenCentral()
mavenCentral()
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
testImplementation("org.junit.jupiter:junit-jupiter:5.6.2")
testImplementation("com.alibaba.testable:testable-all:0.4.4")
testAnnotationProcessor("com.alibaba.testable:testable-processor:0.4.4")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
testImplementation("org.junit.jupiter:junit-jupiter:5.6.2")
testImplementation("com.alibaba.testable:testable-all:0.6.0")
testAnnotationProcessor("com.alibaba.testable:testable-processor:0.6.0")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "1.8"
}
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "1.8"
}
}
tasks.withType<Test> {
jvmArgs("-javaagent:${classpath.find { it.name.contains("testable-agent") }!!.absolutePath}")
useJUnitPlatform()
jvmArgs("-javaagent:${classpath.find { it.name.contains("testable-agent") }!!.absolutePath}")
useJUnitPlatform()
}

View File

@@ -1,119 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.testable</groupId>
<artifactId>kotlin-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>kotlin-demo</name>
<description>Demo project for TestableMock</description>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.testable</groupId>
<artifactId>kotlin-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>kotlin-demo</name>
<description>Demo project for TestableMock</description>
<properties>
<java.version>1.8</java.version>
<kotlin.version>1.3.72</kotlin.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>5.6.2</junit.version>
<testable.version>0.4.4</testable.version>
</properties>
<properties>
<java.version>1.8</java.version>
<kotlin.version>1.3.72</kotlin.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>5.6.2</junit.version>
<testable.version>0.6.0</testable.version>
</properties>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-all</artifactId>
<version>${testable.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependency>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-all</artifactId>
<version>${testable.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<!-- <configuration>-->
<!-- <argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>-->
<!-- </configuration>-->
</plugin>
<plugin>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-maven-plugin</artifactId>
<version>${testable.version}</version>
<executions>
<execution>
<id>prepare</id>
<goals>
<goal>prepare</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.6</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>target/jacoco.exec</dataFile>
<outputDirectory>target/jacoco-ut</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<!-- <configuration>-->
<!-- <argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>-->
<!-- </configuration>-->
</plugin>
<plugin>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-maven-plugin</artifactId>
<version>${testable.version}</version>
<executions>
<execution>
<id>prepare</id>
<goals>
<goal>prepare</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.6</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>target/jacoco.exec</dataFile>
<outputDirectory>target/jacoco-ut</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,9 +1,13 @@
package com.alibaba.testable.demo
package com.alibaba.demo.basic
import com.alibaba.testable.demo.model.BlackBox
import com.alibaba.testable.demo.model.Box
import com.alibaba.testable.demo.model.Color
import com.alibaba.demo.basic.model.mock.BlackBox
import com.alibaba.demo.basic.model.mock.Box
import com.alibaba.demo.basic.model.mock.Color
/**
* 演示父类变量引用子类对象时的Mock场景
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
*/
class DemoInherit {
/**

View File

@@ -0,0 +1,49 @@
package com.alibaba.demo.basic
import java.util.concurrent.ExecutionException
import java.util.concurrent.Executors
/**
* 演示对内部类的Mock支持
* Demonstrate support for mocking invocation inside a inner class
*/
class DemoInnerClass {
class StaticInner {
/**
* invocation inside a static inner class
*/
fun demo(): String {
return methodToBeMock()
}
}
inner class Inner {
/**
* invocation inside a non-static inner class
*/
fun demo(): String {
return methodToBeMock()
}
}
private val executorService = Executors.newSingleThreadExecutor()
@Throws(ExecutionException::class, InterruptedException::class)
fun callAnonymousInner(): String {
/**
* invocation inside a anonymous inner class
*/
val future = executorService.submit<String> { methodToBeMock() }
return future.get()
}
fun callInnerDemo(): String {
return Inner().demo()
}
companion object {
fun methodToBeMock(): String {
return "RealCall"
}
}
}

View File

@@ -1,10 +1,11 @@
package com.alibaba.testable.demo
package com.alibaba.demo.basic
import com.alibaba.testable.demo.model.BlackBox
import com.alibaba.demo.basic.model.mock.BlackBox
import java.util.*
/**
* @author flin
* 演示Mock方法调用校验器
* Demonstrate mock method invocation verifier
*/
class DemoMatcher {
/**

View File

@@ -1,10 +1,14 @@
package com.alibaba.testable.demo
package com.alibaba.demo.basic
import com.alibaba.testable.demo.model.BlackBox
import com.alibaba.testable.demo.model.ColorBox
import com.alibaba.demo.basic.model.mock.BlackBox
import com.alibaba.demo.basic.model.mock.ColorBox
import java.nio.file.Files
import java.nio.file.Paths
/**
* 演示基本的Mock功能
* Demonstrate basic mock functionality
*/
class DemoMock {
/**
@@ -18,7 +22,7 @@ class DemoMock {
* method with member method invoke
*/
fun outerFunc(s: String): String {
return "{ \"res\": \"" + innerFunc(s) + "\"}"
return "{ \"res\": \"" + innerFunc(s) + staticFunc() + "\"}"
}
/**
@@ -52,4 +56,14 @@ class DemoMock {
}
private fun callFromDifferentMethod() = "realOne"
companion object {
private fun staticFunc(): String {
return "_STATIC_TAIL"
}
// fun callStaticFunc(): String {
// return "CALL${staticFunc()}"
// }
}
}

View File

@@ -0,0 +1,48 @@
package com.alibaba.demo.basic
/**
* 演示私有成员访问功能
* Demonstrate private member access functionality
*/
class DemoPrivateAccess {
/**
* a private member field
*/
var count = 0
/**
* a constant field
*/
val pi = 3.14
/**
* private member method
*/
fun privateFunc(list: List<String>, str: String, i: Int): String {
return list.reduce { a: String, s: String -> a + s } + " + " + "$str + $i"
}
companion object {
/**
* a private static field
*/
var staticCount = 0
/**
* private static method
*/
fun privateStaticFunc(str: String, i: Int): String {
return "$str + $i"
}
/**
* private jvm static method
*/
@JvmStatic private fun privateJvmStaticFunc(list: List<String>, str: String, i: Int): String {
return list.reduce { a: String, s: String -> a + s } + " * " + "$str * $i"
}
}
}

View File

@@ -1,9 +1,13 @@
package com.alibaba.testable.demo
package com.alibaba.demo.basic
import java.util.ArrayList
import java.util.HashMap
import java.util.HashSet
/**
* 演示模板方法的Mock场景
* Demonstrate scenario of mocking template method
*/
class DemoTemplate {
private fun <T> getList(value: T): List<T> {
val l: MutableList<T> = ArrayList()

View File

@@ -1,4 +1,4 @@
package com.alibaba.testable.demo.model
package com.alibaba.demo.basic.model.mock
class BlackBox(var input: String) : Box(), Color {

View File

@@ -1,4 +1,4 @@
package com.alibaba.testable.demo.model
package com.alibaba.demo.basic.model.mock
abstract class Box {

View File

@@ -1,4 +1,4 @@
package com.alibaba.testable.demo.model
package com.alibaba.demo.basic.model.mock
interface Color {

View File

@@ -0,0 +1,20 @@
package com.alibaba.demo.basic.model.omni
/**
* 我是一个只有私有构造方法的类
* This class have only private constructor
*/
class Child private constructor() {
// ---------- 内部成员字段 ----------
var grandChild: GrandChild? = null
var subChild: InnerChild? = null
/**
* 这是一个私有内部类
* An private inner class
*/
inner class InnerChild {
private val secret: String? = null
}
}

View File

@@ -0,0 +1,9 @@
package com.alibaba.demo.basic.model.omni
class GrandChild {
// ---------- 内部成员字段 ----------
var value = 0
var content: String? = null
}

View File

@@ -0,0 +1,19 @@
package com.alibaba.demo.basic.model.omni
import java.lang.IllegalArgumentException
/**
* 我是一个虽有构造方法,但无法正常构造的类
* This class have constructor with exception throw
*/
class Parent {
init {
throw IllegalArgumentException()
}
// ---------- 内部成员字段 ----------
var child: Child? = null
var children: Array<Child>? = null
}

View File

@@ -1,4 +1,4 @@
package com.alibaba.testable.demo.util
package com.alibaba.demo.java2kotlin
import java.io.File
import java.io.IOException

View File

@@ -0,0 +1,9 @@
package com.alibaba.demo.one2multi
class ASvc {
fun demo(name: String): String {
return String.format("a_%s", name)
}
}

View File

@@ -0,0 +1,9 @@
package com.alibaba.demo.one2multi
class BSvc {
fun demo(name: String): String {
return String.format("b_%s", name)
}
}

View File

@@ -0,0 +1,9 @@
package com.alibaba.demo.one2multi
class CSvc {
fun demo(name: String): String {
return String.format("c_%s", name)
}
}

View File

@@ -1,44 +0,0 @@
package com.alibaba.testable.demo
class DemoPrivateAccess {
/**
* a private member field
*/
private var count = 0
/**
* a constant field
*/
val pi = 3.14
/**
* private member method
*/
private fun privateFunc(s: String, i: Int): String {
return "$s - $i"
}
companion object {
/**
* a private static field
*/
private var staticCount = 0
/**
* private static method
*/
private fun privateStaticFunc(s: String, i: Int): String {
return "$s + $i"
}
/**
* private jvm static method
*/
@JvmStatic private fun privateJvmStaticFunc(s: String, i: Int): String {
return "$s * $i"
}
}
}

View File

@@ -0,0 +1,92 @@
package com.alibaba.demo.basic
import com.alibaba.testable.core.annotation.MockMethod
import com.alibaba.testable.core.matcher.InvokeVerifier
import com.alibaba.demo.basic.model.mock.BlackBox
import com.alibaba.demo.basic.model.mock.Box
import com.alibaba.demo.basic.model.mock.Color
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
/**
* 演示父类变量引用子类对象时的Mock场景
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
*/
internal class DemoInheritTest {
private val demoInherit = com.alibaba.demo.basic.DemoInherit()
class Mock {
@MockMethod(targetMethod = "put")
private fun put_into_box(self: Box, something: String) {
self.put("put_" + something + "_into_box")
}
@MockMethod(targetMethod = "put")
private fun put_into_blackbox(self: BlackBox, something: String) {
self.put("put_" + something + "_into_blackbox")
}
@MockMethod(targetMethod = "get")
private fun get_from_box(self: Box): String {
return "get_from_box"
}
@MockMethod(targetMethod = "get")
private fun get_from_blackbox(self: BlackBox): String {
return "get_from_blackbox"
}
@MockMethod(targetMethod = "getColor")
private fun get_color_from_color(self: Color): String {
return "color_from_color"
}
@MockMethod(targetMethod = "getColor")
private fun get_color_from_blackbox(self: BlackBox): String {
return "color_from_blackbox"
}
}
@Test
fun should_mock_call_sub_object_method_by_parent_object() {
val box = demoInherit.putIntoBox() as BlackBox
InvokeVerifier.verify("put_into_box").withTimes(1)
Assertions.assertEquals("put_data_into_box", box.get())
}
@Test
fun should_mock_call_sub_object_method_by_sub_object() {
val box = demoInherit.putIntoBlackBox()
InvokeVerifier.verify("put_into_blackbox").withTimes(1)
Assertions.assertEquals("put_data_into_blackbox", box.get())
}
@Test
fun should_mock_call_parent_object_method_by_parent_object() {
val content = demoInherit.fromBox
InvokeVerifier.verify("get_from_box").withTimes(1)
Assertions.assertEquals("get_from_box", content)
}
@Test
fun should_mock_call_parent_object_method_by_sub_object() {
val content = demoInherit.fromBlackBox
InvokeVerifier.verify("get_from_blackbox").withTimes(1)
Assertions.assertEquals("get_from_blackbox", content)
}
@Test
fun should_mock_call_interface_method_by_interface_object() {
val color = demoInherit.colorViaColor
InvokeVerifier.verify("get_color_from_color").withTimes(1)
Assertions.assertEquals("color_from_color", color)
}
@Test
fun should_mock_call_interface_method_by_sub_class_object() {
val color = demoInherit.colorViaBox
InvokeVerifier.verify("get_color_from_blackbox").withTimes(1)
Assertions.assertEquals("color_from_blackbox", color)
}
}

View File

@@ -0,0 +1,28 @@
package com.alibaba.demo.basic
import com.alibaba.testable.core.annotation.MockMethod
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
/**
* 演示对内部类的Mock支持
* Demonstrate support for mocking invocation inside a inner class
*/
internal class DemoInnerClassTest {
class Mock {
@MockMethod(targetClass = DemoInnerClass::class)
fun methodToBeMock(): String {
return "MockedCall"
}
}
@Test
@Throws(Exception::class)
fun should_mock_invoke_inside_inner_class() {
val demo = DemoInnerClass()
Assertions.assertEquals("MockedCall", demo.callInnerDemo())
Assertions.assertEquals("MockedCall", demo.callAnonymousInner())
Assertions.assertEquals("MockedCall", DemoInnerClass.StaticInner().demo())
}
}

View File

@@ -1,10 +1,10 @@
package com.alibaba.testable.demo
package com.alibaba.demo.basic
import com.alibaba.testable.core.annotation.MockMethod
import com.alibaba.testable.core.error.VerifyFailedError
import com.alibaba.testable.core.matcher.InvokeMatcher
import com.alibaba.testable.core.matcher.InvokeVerifier
import com.alibaba.testable.demo.model.BlackBox
import com.alibaba.demo.basic.model.mock.BlackBox
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
@@ -14,21 +14,22 @@ import org.junit.jupiter.api.Test
*/
internal class DemoMatcherTest {
private val demoMatcher = DemoMatcher()
private val demoMatcher = com.alibaba.demo.basic.DemoMatcher()
@MockMethod(targetMethod = "methodToBeMocked")
private fun methodWithoutArgument(self: DemoMatcher) {
class Mock {
@MockMethod(targetMethod = "methodToBeMocked")
private fun methodWithoutArgument(self: com.alibaba.demo.basic.DemoMatcher) {
}
@MockMethod(targetMethod = "methodToBeMocked")
private fun methodWithArguments(self: com.alibaba.demo.basic.DemoMatcher, a1: Any, a2: Any) {
}
@MockMethod(targetMethod = "methodToBeMocked")
private fun methodWithArrayArgument(self: com.alibaba.demo.basic.DemoMatcher, a: Array<Any>) {
}
}
@MockMethod(targetMethod = "methodToBeMocked")
private fun methodWithArguments(self: DemoMatcher, a1: Any, a2: Any) {
}
@MockMethod(targetMethod = "methodToBeMocked")
private fun methodWithArrayArgument(self: DemoMatcher, a: Array<Any>) {
}
@Test
fun should_match_no_argument() {
demoMatcher.callMethodWithoutArgument()

View File

@@ -0,0 +1,123 @@
package com.alibaba.demo.basic
import com.alibaba.testable.core.annotation.MockConstructor
import com.alibaba.testable.core.annotation.MockMethod
import com.alibaba.testable.core.matcher.InvokeVerifier.verify
import com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD
import com.alibaba.testable.core.tool.TestableTool.MOCK_CONTEXT
import com.alibaba.demo.basic.model.mock.BlackBox
import com.alibaba.demo.basic.model.mock.ColorBox
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.util.concurrent.Executors
/**
* 演示基本的Mock功能
* Demonstrate basic mock functionality
*/
internal class DemoMockTest {
private val demoMock = DemoMock()
class Mock {
@MockConstructor
private fun createBlackBox(text: String) = BlackBox("mock_$text")
@MockMethod(targetClass = DemoMock::class)
private fun innerFunc(text: String) = "mock_$text"
@MockMethod(targetClass = DemoMock::class)
private fun staticFunc(): String {
return "_MOCK_TAIL";
}
@MockMethod(targetClass = BlackBox::class)
private fun trim() = "trim_string"
@MockMethod(targetClass = BlackBox::class, targetMethod = "substring")
private fun sub(i: Int, j: Int) = "sub_string"
@MockMethod(targetClass = BlackBox::class)
private fun startsWith(s: String) = false
@MockMethod(targetClass = BlackBox::class)
private fun secretBox(): BlackBox {
return BlackBox("not_secret_box")
}
@MockMethod(targetClass = ColorBox::class)
private fun createBox(color: String, box: BlackBox): BlackBox {
return BlackBox("White_${box.get()}")
}
@MockMethod(targetClass = DemoMock::class)
private fun callFromDifferentMethod(): String {
return if (MOCK_CONTEXT["case"] == "special_case") {
"mock_special"
} else {
when (SOURCE_METHOD) {
"callerOne" -> "mock_one"
else -> "mock_others"
}
}
}
}
@Test
fun should_mock_new_object() {
assertEquals("mock_something", demoMock.newFunc())
verify("createBlackBox").with("something")
}
@Test
fun should_mock_member_method() {
assertEquals("{ \"res\": \"mock_hello_MOCK_TAIL\"}", demoMock.outerFunc("hello"))
verify("innerFunc").with("hello")
verify("staticFunc").with()
}
// @Test
// fun should_mock_method_in_companion_object() {
// assertEquals("CALL_MOCK_TAIL", DemoMock.callStaticFunc())
// verify("staticFunc").with()
// }
@Test
fun should_mock_common_method() {
assertEquals("trim_string__sub_string__false", demoMock.commonFunc())
verify("trim").withTimes(1)
verify("sub").withTimes(1)
verify("startsWith").withTimes(1)
}
@Test
fun should_mock_static_method() {
assertEquals("White_not_secret_box", demoMock.getBox().get())
verify("secretBox").withTimes(1)
verify("createBox").withTimes(1)
}
@Test
fun should_get_source_method_name() {
// synchronous
assertEquals("mock_one_mock_others", demoMock.callerOne() + "_" + demoMock.callerTwo())
// asynchronous
assertEquals("mock_one_mock_others", Executors.newSingleThreadExecutor().submit<String> {
demoMock.callerOne() + "_" + demoMock.callerTwo()
}.get())
verify("callFromDifferentMethod").withTimes(4)
}
@Test
fun should_get_test_case_name() {
MOCK_CONTEXT["case"] = "special_case"
// synchronous
assertEquals("mock_special", demoMock.callerOne())
// asynchronous
assertEquals("mock_special", Executors.newSingleThreadExecutor().submit<String> {
demoMock.callerOne()
}.get())
verify("callFromDifferentMethod").withTimes(2)
MOCK_CONTEXT.clear()
}
}

View File

@@ -0,0 +1,86 @@
package com.alibaba.demo.basic
import com.alibaba.demo.basic.model.omni.Child
import com.alibaba.demo.basic.model.omni.Parent
import com.alibaba.testable.core.tool.OmniAccessor
import com.alibaba.testable.core.tool.OmniConstructor
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
/**
* 演示快速创建任意对象和使用路径访问成员
* Demonstrate quick object construction and access members by path
*/
internal class DemoOmniMethodsTest {
@Test
fun should_construct_any_class() {
val parent = OmniConstructor.newInstance(Parent::class.java)
// 任意深度的子孙成员对象都不为空
Assertions.assertNotNull(parent.child?.grandChild?.content)
// 所有基础类型初始化为默认数值
Assertions.assertEquals(0, parent.child?.grandChild?.value)
Assertions.assertEquals("", parent.child?.grandChild?.content)
// 所有数组类型初始化为空数组
Assertions.assertEquals(0, parent.children?.size)
}
@Test
fun should_get_any_member() {
val parent = OmniConstructor.newInstance(Parent::class.java)
parent.children = OmniConstructor.newArray(Child::class.java, 3)
parent.child?.grandChild?.content = "from child"
parent.children?.get(0)?.grandChild?.content = "from 1st children"
parent.children?.get(1)?.grandChild?.content = "from 2nd children"
parent.children?.get(2)?.grandChild?.content = "from 3rd children"
// 使用成员名称快速读取成员对象
var contents = OmniAccessor.get<String?>(parent, "content")
Assertions.assertEquals(4, contents.size)
Assertions.assertEquals("from child", contents[0])
Assertions.assertEquals("from 1st children", contents[1])
Assertions.assertEquals("from 2nd children", contents[2])
Assertions.assertEquals("from 3rd children", contents[3])
// 使用成员类型快速读取成员对象
contents = OmniAccessor.get(parent, "{Child}/{GrandChild}/content")
Assertions.assertEquals(1, contents.size)
Assertions.assertEquals("from child", contents[0])
// 使用带下标的路径读取成员对象
Assertions.assertEquals("from 2nd children", OmniAccessor.getFirst(parent, "children[1]/{GrandChild}/content"))
Assertions.assertEquals("from 3rd children", OmniAccessor.getFirst(parent, "{Child[]}[2]/{GrandChild}/content"))
// 使用模糊路径快速读取成员对象
Assertions.assertEquals("from 1st children", OmniAccessor.getFirst(parent, "{C*[]}[0]/*/con*t"))
}
@Test
fun should_set_any_member() {
val parent = OmniConstructor.newInstance(Parent::class.java)
parent.children = OmniConstructor.newArray(Child::class.java, 3)
// 使用指定路径快速给成员对象赋值
OmniAccessor.set(parent, "child/grandChild/content", "demo child")
Assertions.assertEquals("demo child", parent.child?.grandChild?.content)
// 使用带下标的路径给成员对象赋值
OmniAccessor.set(parent, "children[1]/grandChild/content", "demo children[1]")
Assertions.assertEquals("demo children[1]", parent.children?.get(1)?.grandChild?.content)
// 使用模糊路径批量给成员对象赋值
OmniAccessor.set(parent, "child*/*/content", "demo in batch")
Assertions.assertEquals("demo in batch", parent.child?.grandChild?.content)
Assertions.assertEquals("demo in batch", parent.children?.get(0)?.grandChild?.content)
Assertions.assertEquals("demo in batch", parent.children?.get(1)?.grandChild?.content)
Assertions.assertEquals("demo in batch", parent.children?.get(2)?.grandChild?.content)
// 读写私有内部类类型的成员(使用类型名引用内部类时,无需带外部类名)
Assertions.assertEquals("", OmniAccessor.getFirst(parent, "subChild/secret"))
OmniAccessor.set(parent, "{InnerChild}/secret", "inner-class secret")
Assertions.assertEquals("inner-class secret", OmniAccessor.getFirst(parent, "subChild/secret"))
}
}

View File

@@ -1,6 +1,6 @@
package com.alibaba.testable.demo
package com.alibaba.demo.basic
import com.alibaba.testable.core.accessor.PrivateAccessor
import com.alibaba.testable.core.tool.PrivateAccessor
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
@@ -8,35 +8,37 @@ import org.junit.jupiter.api.Test
* 演示私有成员访问功能
* Demonstrate private member access functionality
*/
internal class DemoPrivateAccessTest {
internal class DemoPrivateAccessorTest {
private val demoPrivateAccess = DemoPrivateAccess()
@Test
fun should_able_to_access_private_method() {
assertEquals("hello - 1", PrivateAccessor.invoke(demoPrivateAccess, "privateFunc", "hello", 1))
fun should_access_private_method() {
val list = listOf("a", "b", "c");
assertEquals("abc + hello + 1", PrivateAccessor.invoke(demoPrivateAccess, "privateFunc", list, "hello", 1))
}
@Test
fun should_able_to_access_private_field() {
fun should_access_private_field() {
PrivateAccessor.set(demoPrivateAccess, "count", 3)
assertEquals(3, PrivateAccessor.get(demoPrivateAccess, "count"))
}
@Test
fun should_able_to_access_private_static_method() {
fun should_access_private_static_method() {
val list = listOf("a", "b", "c");
assertEquals("hello + 1", PrivateAccessor.invokeStatic(DemoPrivateAccess::class.java, "privateStaticFunc", "hello", 1))
assertEquals("hello * 1", PrivateAccessor.invokeStatic(DemoPrivateAccess::class.java, "privateJvmStaticFunc", "hello", 1))
assertEquals("abc * hello * 1", PrivateAccessor.invokeStatic(DemoPrivateAccess::class.java, "privateJvmStaticFunc", list, "hello", 1))
}
@Test
fun should_able_to_access_private_static_field() {
fun should_access_private_static_field() {
PrivateAccessor.setStatic(DemoPrivateAccess::class.java, "staticCount", 3)
assertEquals(3, PrivateAccessor.getStatic(DemoPrivateAccess::class.java, "staticCount"))
}
@Test
fun should_able_to_update_final_field() {
fun should_update_final_field() {
PrivateAccessor.set(demoPrivateAccess, "pi", 4.13)
assertEquals(4.13, demoPrivateAccess.pi)
}

View File

@@ -0,0 +1,63 @@
package com.alibaba.demo.basic
import com.alibaba.testable.core.annotation.MockConstructor
import com.alibaba.testable.core.annotation.MockMethod
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import java.util.*
/**
* 演示模板方法的Mock场景
* Demonstrate scenario of mocking template method
*/
internal class DemoTemplateTest {
private val demoTemplate = DemoTemplate()
class Mock {
@MockMethod
private fun <T> getList(self: DemoTemplate, value: T): List<T> {
return mutableListOf((value.toString() + "_mock_list") as T)
}
@MockMethod
private fun <K, V> getMap(self: DemoTemplate, key: K, value: V): Map<K, V> {
return mutableMapOf(key to (value.toString() + "_mock_map") as V)
}
@MockConstructor
private fun newHashSet(): HashSet<*> {
val set = HashSet<Any>()
set.add("insert_mock")
return set
}
@MockMethod
private fun <E> add(s: MutableSet<E>, e: E): Boolean {
s.add((e.toString() + "_mocked") as E)
return true
}
}
@Test
fun should_mock_single_template_method() {
val res = demoTemplate.singleTemplateMethod()
Assertions.assertEquals("demo_mock_list", res)
}
@Test
fun should_mock_double_template_method() {
val res = demoTemplate.doubleTemplateMethod()
Assertions.assertEquals("testable_mock_map", res)
}
@Test
fun should_mock_new_template_method() {
val res = demoTemplate.newTemplateMethod()
Assertions.assertEquals(2, res.size)
val iterator = res.stream().iterator()
Assertions.assertEquals("insert_mock", iterator.next())
Assertions.assertEquals("world_mocked", iterator.next())
}
}

View File

@@ -0,0 +1,50 @@
package com.alibaba.demo.java2kotlin
import com.alibaba.testable.core.annotation.MockMethod
import com.alibaba.testable.core.matcher.InvokeVerifier.verify
import org.junit.jupiter.api.Test
import java.io.File
class PathUtilTest {
class Mock {
@MockMethod
fun exists(f: File): Boolean {
return when (f.absolutePath) {
"/a/b" -> true
"/a/b/c" -> true
else -> f.exists()
}
}
@MockMethod
fun isDirectory(f: File): Boolean {
return when (f.absolutePath) {
"/a/b/c" -> true
else -> f.isDirectory
}
}
@MockMethod
fun delete(f: File): Boolean {
return true
}
@MockMethod
fun listFiles(f: File): Array<File>? {
return when (f.absolutePath) {
"/a/b" -> arrayOf(File("/a/b/c"), File("/a/b/d"))
"/a/b/c" -> arrayOf(File("/a/b/c/e"))
else -> f.listFiles()
}
}
}
@Test
fun should_mock_java_method_invoke_in_kotlin() {
PathUtil.deleteRecursively(File("/a/b/"))
verify("listFiles").withTimes(2)
verify("delete").withTimes(4)
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,25 @@
package com.alibaba.demo.one2multi
import com.alibaba.testable.core.annotation.MockWith
import com.alibaba.testable.core.matcher.InvokeVerifier.verify
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
@MockWith
class OneToMultiSvcTest {
private val aSvc = ASvc()
private val bSvc = BSvc()
private val cSvc = CSvc()
@Test
fun should_test_multi_class_together() {
Assertions.assertEquals("a_mock", aSvc.demo("test"))
Assertions.assertEquals("b_mock", bSvc.demo("test"))
Assertions.assertEquals("c_mock", cSvc.demo("test"))
verify("a_format").withTimes(1)
verify("b_format").withTimes(1)
verify("c_format").withTimes(1)
}
}

View File

@@ -1,91 +0,0 @@
package com.alibaba.testable.demo
import com.alibaba.testable.core.annotation.MockMethod
import com.alibaba.testable.core.matcher.InvokeVerifier
import com.alibaba.testable.demo.model.BlackBox
import com.alibaba.testable.demo.model.Box
import com.alibaba.testable.demo.model.Color
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
/**
* 演示父类变量引用子类对象时的Mock场景
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
*/
internal class DemoInheritTest {
private val demoInherit = DemoInherit()
@MockMethod(targetMethod = "put")
private fun put_into_box(self: Box, something: String) {
self.put("put_" + something + "_into_box")
}
@MockMethod(targetMethod = "put")
private fun put_into_blackbox(self: BlackBox, something: String) {
self.put("put_" + something + "_into_blackbox")
}
@MockMethod(targetMethod = "get")
private fun get_from_box(self: Box): String {
return "get_from_box"
}
@MockMethod(targetMethod = "get")
private fun get_from_blackbox(self: BlackBox): String {
return "get_from_blackbox"
}
@MockMethod(targetMethod = "getColor")
private fun get_color_from_color(self: Color): String {
return "color_from_color"
}
@MockMethod(targetMethod = "getColor")
private fun get_color_from_blackbox(self: BlackBox): String {
return "color_from_blackbox"
}
@Test
fun should_able_to_mock_call_sub_object_method_by_parent_object() {
val box = demoInherit.putIntoBox() as BlackBox
InvokeVerifier.verify("put_into_box").withTimes(1)
Assertions.assertEquals("put_data_into_box", box.get())
}
@Test
fun should_able_to_mock_call_sub_object_method_by_sub_object() {
val box = demoInherit.putIntoBlackBox()
InvokeVerifier.verify("put_into_blackbox").withTimes(1)
Assertions.assertEquals("put_data_into_blackbox", box.get())
}
@Test
fun should_able_to_mock_call_parent_object_method_by_parent_object() {
val content = demoInherit.fromBox
InvokeVerifier.verify("get_from_box").withTimes(1)
Assertions.assertEquals("get_from_box", content)
}
@Test
fun should_able_to_mock_call_parent_object_method_by_sub_object() {
val content = demoInherit.fromBlackBox
InvokeVerifier.verify("get_from_blackbox").withTimes(1)
Assertions.assertEquals("get_from_blackbox", content)
}
@Test
fun should_able_to_mock_call_interface_method_by_interface_object() {
val color = demoInherit.colorViaColor
InvokeVerifier.verify("get_color_from_color").withTimes(1)
Assertions.assertEquals("color_from_color", color)
}
@Test
fun should_able_to_mock_call_interface_method_by_sub_class_object() {
val color = demoInherit.colorViaBox
InvokeVerifier.verify("get_color_from_blackbox").withTimes(1)
Assertions.assertEquals("color_from_blackbox", color)
}
}

View File

@@ -1,108 +0,0 @@
package com.alibaba.testable.demo
import com.alibaba.testable.core.annotation.MockConstructor
import com.alibaba.testable.core.annotation.MockMethod
import com.alibaba.testable.core.matcher.InvokeVerifier.verify
import com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD
import com.alibaba.testable.core.tool.TestableTool.TEST_CASE
import com.alibaba.testable.demo.model.BlackBox
import com.alibaba.testable.demo.model.ColorBox
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.util.concurrent.Executors
/**
* 演示基本的Mock功能
* Demonstrate basic mock functionality
*/
internal class DemoMockTest {
private val demoMock = DemoMock()
@MockConstructor
private fun createBlackBox(text: String) = BlackBox("mock_$text")
@MockMethod
private fun innerFunc(self: DemoMock, text: String) = "mock_$text"
@MockMethod
private fun trim(self: BlackBox) = "trim_string"
@MockMethod(targetMethod = "substring")
private fun sub(self: BlackBox, i: Int, j: Int) = "sub_string"
@MockMethod
private fun startsWith(self: BlackBox, s: String) = false
@MockMethod
private fun secretBox(ignore: BlackBox): BlackBox {
return BlackBox("not_secret_box")
}
@MockMethod
private fun createBox(ignore: ColorBox, color: String, box: BlackBox): BlackBox {
return BlackBox("White_${box.get()}")
}
@MockMethod
private fun callFromDifferentMethod(self: DemoMock): String {
return if (TEST_CASE == "should_able_to_get_test_case_name") {
"mock_special"
} else {
when (SOURCE_METHOD) {
"callerOne" -> "mock_one"
else -> "mock_others"
}
}
}
@Test
fun should_able_to_mock_new_object() {
assertEquals("mock_something", demoMock.newFunc())
verify("createBlackBox").with("something")
}
@Test
fun should_able_to_mock_member_method() {
assertEquals("{ \"res\": \"mock_hello\"}", demoMock.outerFunc("hello"))
verify("innerFunc").with("hello")
}
@Test
fun should_able_to_mock_common_method() {
assertEquals("trim_string__sub_string__false", demoMock.commonFunc())
verify("trim").withTimes(1)
verify("sub").withTimes(1)
verify("startsWith").withTimes(1)
}
@Test
fun should_able_to_mock_static_method() {
assertEquals("White_not_secret_box", demoMock.getBox().get())
verify("secretBox").withTimes(1)
verify("createBox").withTimes(1)
}
@Test
fun should_able_to_get_source_method_name() {
// synchronous
assertEquals("mock_one_mock_others", demoMock.callerOne() + "_" + demoMock.callerTwo())
// asynchronous
assertEquals("mock_one_mock_others", Executors.newSingleThreadExecutor().submit<String> {
demoMock.callerOne() + "_" + demoMock.callerTwo()
}.get())
verify("callFromDifferentMethod").withTimes(4)
}
@Test
fun should_able_to_get_test_case_name() {
// synchronous
assertEquals("mock_special", demoMock.callerOne())
// asynchronous
assertEquals("mock_special", Executors.newSingleThreadExecutor().submit<String> {
demoMock.callerOne()
}.get())
verify("callFromDifferentMethod").withTimes(2)
}
}

View File

@@ -1,62 +0,0 @@
package com.alibaba.testable.demo
import com.alibaba.testable.core.annotation.MockConstructor
import com.alibaba.testable.core.annotation.MockMethod
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import java.util.*
/**
* 演示模板方法可以被Mock
* Demonstrate template method can be mocked
*/
internal class DemoTemplateTest {
private val demoTemplate = DemoTemplate()
@MockMethod
private fun <T> getList(self: DemoTemplate, value: T): List<T> {
return mutableListOf((value.toString() + "_mock_list") as T)
}
@MockMethod
private fun <K, V> getMap(self: DemoTemplate, key: K, value: V): Map<K, V> {
return mutableMapOf(key to (value.toString() + "_mock_map") as V)
}
@MockConstructor
private fun newHashSet(): HashSet<*> {
val set = HashSet<Any>()
set.add("insert_mock")
return set
}
@MockMethod
private fun <E> add(s: MutableSet<E>, e: E): Boolean {
s.add((e.toString() + "_mocked") as E)
return true
}
@Test
fun should_able_to_mock_single_template_method() {
val res = demoTemplate.singleTemplateMethod()
Assertions.assertEquals("demo_mock_list", res)
}
@Test
fun should_able_to_mock_double_template_method() {
val res = demoTemplate.doubleTemplateMethod()
Assertions.assertEquals("testable_mock_map", res)
}
@Test
fun should_able_to_mock_new_template_method() {
val res = demoTemplate.newTemplateMethod()
Assertions.assertEquals(2, res.size)
val iterator = res.stream().iterator()
Assertions.assertEquals("insert_mock", iterator.next())
Assertions.assertEquals("world_mocked", iterator.next())
}
}

View File

@@ -1,48 +0,0 @@
package com.alibaba.testable.demo.util
import com.alibaba.testable.core.annotation.MockMethod
import com.alibaba.testable.core.matcher.InvokeVerifier.verify
import org.junit.jupiter.api.Test
import java.io.File
class PathUtilTest {
@MockMethod
fun exists(f: File): Boolean {
return when (f.absolutePath) {
"/a/b" -> true
"/a/b/c" -> true
else -> f.exists()
}
}
@MockMethod
fun isDirectory(f: File): Boolean {
return when (f.absolutePath) {
"/a/b/c" -> true
else -> f.isDirectory
}
}
@MockMethod
fun delete(f: File): Boolean {
return true
}
@MockMethod
fun listFiles(f: File): Array<File>? {
return when (f.absolutePath) {
"/a/b" -> arrayOf(File("/a/b/c"), File("/a/b/d"))
"/a/b/c" -> arrayOf(File("/a/b/c/e"))
else -> f.listFiles()
}
}
@Test
fun should_able_to_mock_java_method_invoke_in_kotlin() {
PathUtil.deleteRecursively(File("/a/b/"))
verify("listFiles").withTimes(2)
verify("delete").withTimes(4)
}
}

View File

@@ -1,11 +1,11 @@
TestableMock Introduction
---
The Mock method in unit testing is usually to bypass method calls that rely on external resources or irrelevant functions, so that the focus of the test can be keep on the code logic that needs to be verified and guaranteed.
The mock method in unit testing is usually to bypass method calls that rely on external resources or irrelevant functions, so that the focus of the test can be keep on the code logic that needs to be verified and guaranteed.
When defining the Mock method, the developer really cares about only one thing: "<u>This call should be replaced with the fake **mock method** during testing</u>".
When defining the mock method, developers really care about only one thing: "<u>This call should be replaced with the fake **mock method** during testing</u>".
However, when the current mainstream Mock framework implements the Mock function, developers have to worry about too many things: how the Mock framework is initialized, whether it is compatible with the unit testing framework used, whether the method to be mocked is private or static, whether the Mock object is created by `new` operator or injected, how to send the mock object back to the class under test... These non-critical additional tasks greatly distract the fun of using the mock tool.
However, when the current mainstream mock framework implements the mock function, developers have to worry about too many things: how the mock framework is initialized, whether it is compatible with the unit testing framework used, whether the method to be mocked is private or static, whether the object to be mocked is created by `new` operator or injected, how to send the mock object back to the class under test... These non-critical additional tasks greatly distract the fun of using the mock tool.
Therefore, we developed `TestableMock`, **a maverick and lightweight mock tool**.

View File

@@ -0,0 +1,9 @@
## About Us
The core development team of `TestableMock` comes from **Alibaba Cloud · Cloud R&D Department** (formerly known as **Alibaba Group R&D Efficiency Department**). We not only internally support Alibaba Group's end-to-end project code, testing, and release process of thousands of BU-level product lines, but also the product R&D team of Alibaba Cloud enterprise-level R&D collaboration platform [云效](https://www.aliyun.com/product/yunxiao). As has been deeply involved in the field of developer tools, we are committed to improving the production experience of developers and building a digital R&D ecosystem for enterprises.
The function of this project is inspired by our summary of the pain points that Java developers often encounter in daily unit testing ([external dependence on Mock is cumbersome](en-us/doc/use-mock.md), [private method is difficult to test](en-us/doc/private-accessor.md), [void type method is difficult to test](en-us/doc/test-void-method.md), and [complex parameters are difficult to construct](en-us/doc/parameter-constructor.md)). Its internal name is `Testable`, and was renamed `TestableMock` for SEO reason when it was open sourced, thus the lightweight mock function is highlighted. Don't be fooled by its name, `TestableMock` is more than just a mocking tool.
From the incubation of hackathon, to internal open source, and then to external open source, `TestableMock` has accumulated a group of developer users in the internal and external communities. At the same time, we are constantly improving the functional richness and stability of `TestableMock` itself. According to the current version release process, we usually update the internal beta version containing the `SNAPSHOT` mark internally, and release the official version to the Maven central repository (except for the severe bug fix version) after at least one day of stable used to ensure the update will bring more convenience and less burden to everyone.
Finally, we are relatively optimistic about the discussion in the community about "whether too powerful test assistance is equivalent to condoning code corruption". Just as the birth of `PowerMock` did not really spawn more bad projects, but actually solved many test problems left over by the Java language. Rather than use "code tricks" to test functions indirectly, it could be worth to just remove the constraints, and let the unit test break in all its fury! 🤠

View File

@@ -0,0 +1,4 @@
Available Annotations
---
TO BE TRANSLATED

View File

@@ -1,9 +1,9 @@
Mock Tools Comparison
---
Besides `TestableMock`, there are also several other community Mock tools, such as `Mockito`, `Spock`, `PowerMock` and `JMockit`. Comparison as follows:
Besides `TestableMock`, there are also several other community mock tools, such as `Mockito`, `Spock`, `PowerMock` and `JMockit`. Comparison as follows:
| Tool | Mechanism | Minimal Mock Unit | Limitation of method be mocked | Ease of use | IDE support |
| Tool | Mechanism | Minimal mock unit | Limitation of method be mocked | Ease of use | IDE support |
| ---- | ---- | ---- | ---- | ---- | ---- |
| Mockito | Dynamic proxy | Class | Except private/static method and constructor | **Easy** | **Very well** |
| Spock | Dynamic proxy | Class | Except private/static method and constructor | Complicate | Just so so |
@@ -23,4 +23,4 @@ The functionality of `TestabledMock` is basically the same as that of `PowerMock
The main disadvantage of the current `TestableMock` is that the IDE cannot promptly prompt whether the method parameters are matched correctly when writing the mock method. If the mocking effect does not meet expectation, it has to be verified during runtime through the method provided in the [self-help troubleshooting](en-us/doc/troubleshooting.md) document. This feature needs to be provided by extending IDE plugins in the future.
In addition, because `TestableMock` uses the mock mechanism based on a single method in a unique way, it decouples the mock method definition from the unit test cases. Thus, the mock methods are by default reusable, and the unit test cases become cleaner and purer. On the other hand, the mock methods become fragmented, and life cycle management is relatively difficult. Therefore, it may take some time for developers get used to it.
In addition, because `TestableMock` uses the mechanism based on "each source class binding to its own mock class" in a unique way, it decouples the mock method definition from the unit test cases. Thus, the mock methods are by default reusable, and the unit test cases become cleaner and purer. On the other hand, the mock methods become fragmented, and life cycle management is relatively difficult. Therefore, it may take some time for developers get used to it.

View File

@@ -0,0 +1,4 @@
How Mock Works
---
TO BE TRANSLATED

View File

@@ -1,8 +1,8 @@
Feedback Channel
---
If you have any questions or suggestions during the use of `TestableMock`, please go ahead raise them in [Project Issue](https://github.com/alibaba/testable-mock/issues) and we will usually reply within 24 hours.
If you have any questions or suggestions during the use of `TestableMock`, please go ahead raise them at [Github Issues](https://github.com/alibaba/testable-mock/issues) and we will usually reply within 24 hours.
Please describe the situation in detail. If you think the problem may be related to the potential BUG of `TestableMock`, please try to provide reproducible source code or examples for shorten the troubleshooting time.
Description of the situation you meet in detail is highly welcomed. If the problem may be related to a potential BUG of `TestableMock`, please try to provide reproducible source code or examples for shorten the troubleshooting time.
`TestableMock`, with no more untestable code in Java : )

View File

@@ -5,11 +5,11 @@ Frequently Asked Questions
Create the class under test object directly, and then use the ability of `TestableMock` to access private members to directly assign values to these fields.
#### 2. Can `TestableMock` be used with other Mock tools?
#### 2. Can `TestableMock` be used with other mock tools?
`TestableMock` can be safely used together with other Mock tools based on dynamic proxy mechanism, such as `Mockito`, `EasyMock`, `Spock`, etc., all belong to this category.
`TestableMock` can be safely used together with other mock tools based on dynamic proxy mechanism, such as `Mockito`, `EasyMock`, `Spock`, etc., all belong to this category.
For Mock tools that modify the class loader or the bytecode of the class under test, such as `PowerMock` and `JMockit`, there is no yet case to prove that they will conflict with `TestableMock`, but in principle, there may be a risk of incompatibility between the two, Please use with caution.
For mock tools that modify the class loader or the bytecode of the class under test, such as `PowerMock` and `JMockit`, there is no yet case to prove that they will conflict with `TestableMock`, but in principle, there may be a risk of incompatibility between the two, Please use with caution.
#### 3. How to implement the mock method when the parent class variable points to the child class object?
@@ -17,32 +17,38 @@ 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 first parameter type of the mock method should use the corresponding parent type (or interface) type.
See the use case of the `DemoInheritTest` test class in the Java and Kotlin examples.
#### 4. How to mock generic methods (template methods)?
Same as the Mock method of the ordinary method, just use the same generic parameters directly on the Mock method.
Just use the same generic parameters directly on the mock method.
See the use case of the `DemoTemplateTest` test class in the Java and Kotlin examples.
> Because JVM has a generic erasure mechanism, you can also directly use the `Object` type to replace generic parameters for Java projects, see the commented out "Second solution" example in the Java version of the `DemoTemplateTest` test class.
#### 5. Why mocking methods in the `String` class in the Kotlin project does not work?
#### 5. How to mock invocation inside an inner class?
The `String` type in Kotlin language is actually `kotlin.String` instead of `java.lang.String`. However, when this type is built from bytecode, it will be replaced with Java's `java.lang.String` class, so no matter if the Mock target is written as `kotlin.String` or `java.lang.String`, it cannot match the original called method.
Just put mock methods in the mock container class of its outer class, it works for code in all inner classes.
See the use case of the `DemoInnerClass` test class in the Java and Kotlin examples.
#### 6. Why mocking methods in the `String` class in the Kotlin project does not work?
The `String` type in Kotlin language is actually `kotlin.String` instead of `java.lang.String`. However, when this type is built from bytecode, it will be replaced with Java's `java.lang.String` class, so no matter if the mock target is written as `kotlin.String` or `java.lang.String`, it cannot match the original called method.
In actual scenarios, there are very few scenarios where methods in the `String` class need to be mocked, so `TestableMock` has not dealt with this situation specifically.
#### 6. When trigger a single test case in IntelliJ IDE, why class with `@EnablePrivateAccess` annotation report still private member access errors?
The default compiler provided by IntelliJ handle the annotation processor of the `JSR-269` specification in an incompatible way of maven. You can turn on the "Delegate IDE build/run actions to maven" option in "Build Tools > Maven > Runner" of IntelliJ system configuration:
![delegate-ide-build-to-maven](https://testable-code.oss-cn-beijing.aliyuncs.com/delegate-ide-build-to-maven.png)
#### 7. Can `TestableMock` be used for testing Android projects?
It can be used in combination with [Roboelectric](https://github.com/robolectric/robolectric) testing framework.
The `Dalvik` and `ART` virtual machines of the Android system use a bytecode system different from the standard JVM, which will affect the normal functionality of `TestableMock`. The `Roboelectric` framework can run Android unit tests on a standard JVM virtual machine, which is much faster than running unit tests through the Android virtual machine. Recently, most Android App unit tests are written with the `Roboelectric` framework.
#### 8. Meet "Command Line is too Long. Shorten command line for ..." error when triggering test in IntelliJ IDE?
This problem is caused by the system `Class Path` content is too long, and has nothing to do with `TestableMock`. However, it should be noted that IntelliJ provides two auxiliary solutions: `JAR manifest` and `classpath file`. If `TestableMock` is used in the test, please select `JAR manifest`.
![jar-manifest](https://testable-code.oss-cn-beijing.aliyuncs.com/jar-manifest.png)

View File

@@ -0,0 +1,4 @@
Available Global Arguments
---
TO BE TRANSLATED

View File

@@ -0,0 +1,4 @@
Reusing Mock Methods
---
TO BE TRANSLATED

View File

@@ -0,0 +1,6 @@
Parameter constructor
---
No matter how intricate the parameter structure required by the method under test is, even there is no suitable construction method, or even there are private internal class objects... Call `TestableMock`, the parameter object will be handed to you immediately~
This feature is planned to be released in the `0.6` version.

View File

@@ -9,26 +9,42 @@ In addition, before unit testing begin, it is often necessary to initialize spec
Just add `@EnablePrivateAccess` annotation to the test class, then you have got the following enhancements in the test case:
- Invoke private methods (including static methods) of the class under test
- Read private fields (including static fields) of the class under test
- Modify private fields (including static fields) of the class under test
- Modify the constant fields of the class under test (fields modified with final, including static fields)
- Invoke private methods (including static methods) of the **class under test**
- Read private fields (including static fields) of the **class under test**
- Modify private fields (including static fields) of the **class under test**
- Modify the constant fields of the **class under test** (fields modified with final, including static fields)
When accessing and modifying private and constant members, the IDE may prompt some syntax errors, but the compiler will be able to run the test normally.
For the effect, see the use case in the test class of the `java-demo` sample project `DemoPrivateAccessTest`. (Using compile-time code enhancement, currently only the adaptation of the Java language is implemented)
> This function assumes that the test class is in the same package as the class under test, and the name is `<ClassUnderTest>+Test`. When this convention is not met, you can use the `srcClass` parameter on the `@EnablePrivateAccess` annotation to specify the actual class under test. E.g:
>
> ```java
> @EnablePrivateAccess(srcClass = DemoServiceImpl.class)
> class DemoServiceTest() { ... }
> ```
### Solution 2: Use the `PrivateAccessor` tool class
If you don't want to see the IDE's syntax error reminder, or in a non-Java language JVM project (such as Kotlin language), you can also use the `PrivateAccessor` tool class to directly access private members.
This class provides 6 static methods:
This class provides 7 static methods:
- `PrivateAccessor.get(<ObjectUnderTest>, "<private-field-name>")` ➜ read the private field of the class under test
- `PrivateAccessor.set(<ObjectUnderTest>, "<private-field-name>", <new-value>)` ➜ modify the private field (or constant field) of the class under test
- `PrivateAccessor.invoke(<ObjectUnderTest>, "<private-method-name>", <call-parameters>..)` ➜ call the private method of the class under test
- `PrivateAccessor.getStatic(<ClassUnderTest>, "<private-static-field-name>")` ➜ read the **static** private field of the class under test
- `PrivateAccessor.setStatic(<ClassUnderTest>, "<private-static-field-name>", <new-value>)` ➜ modify the **static** private field (or **static** constant field) of the class under test
- `PrivateAccessor.invokeStatic(<ClassUnderTest>, "<private-static-method-name>", <call-parameters>..)` ➜ call the **static** private method of the class under test
- `PrivateAccessor.get(<AnyObject>, "<private-field-name>")` ➜ read the private field of any object
- `PrivateAccessor.set(<AnyObject>, "<private-field-name>", <new-value>)` ➜ modify the private field (or constant field) of any object
- `PrivateAccessor.invoke(<AnyObject>, "<private-method-name>", <call-parameters>...)` ➜ call the private method of any object
- `PrivateAccessor.getStatic(<AnyClass>, "<private-static-field-name>")` ➜ read the **static** private field of any class
- `PrivateAccessor.setStatic(<AnyClass>, "<private-static-field-name>", <new-value>)` ➜ modify the **static** private field (or **static** constant field) of any class
- `PrivateAccessor.invokeStatic(<AnyClass>, "<private-static-method-name>", <call-parameters>...)` ➜ call the **static** private method of any class
- `PrivateAccessor.construct(<AnyClass>, <constructor-parameters>...)` ➜ create a new object by the private constructor of any class
> Using the `PrivateAccessor` class does not require the test class to have `@EnablePrivateAccess` annotation, but adding this annotation will enable the compile-time verification for the private members of the class under test.
For details, see the use cases in the test classes of the `java-demo` and `kotlin-demo` sample projects `DemoPrivateAccessTest`.
### Compile-time verification of private members
Both of the above two methods essentially use JVM reflection mechanism to achieve private member access, but the JVM compiler will not check the existence of the reflection target. When the code is refactored, if the private method names and parameters in the source class are modified, it would cause exceptions to be discovered only when the unit test is triggered. For this reason, another function of the `@EnablePrivateAccess` annotation is to perform additional compile-time checks for existence of private member of the **class under test**.
**Note**: When the private member verification function is enabled, the `PrivateAccessor` class can only be used to access the private members of the **class under test**, which will help limit the using of the `PrivateAccessor` tool class for "unauthorized" operations unrelated to the current test. If you really need to access private members of other classes, you can remove the `@EnablePrivateAccess` annotation, or set the `verifyTargetOnCompile` parameter of the annotation to `false` to manually turn off the verification function.

View File

@@ -1,5 +1,60 @@
# Release Note
## 0.6.0
- add `OmniConstructor` and `OmniAccessor` tool for parameter preparation
- fix a `ClassFormatError` caused by incorrect `FRAME FULL` operation
- remove support for`diagnose` parameter of `@MockWith` annotation
## 0.5.2
- support use`PrivateAccessor` to access private member in parent class
- fix a class format error in jvm 1.8+ cause by incorrect bytecode generation
- fix illegal field modifiers error when add mock class to an interface
## 0.5.1
- use kotlin-style method descriptor in `VERBOSE` diagnose logs
- support parameterized test of JUnit 5
- fix an exception caused by method parameter with ternary operator
- fix a bug cause log message lost when `@MockWith` annotation used
## 0.4.12
- support verbose diagnose log for better self-troubleshooting
- support disable private access target existence check
- support specify mock scanning packages
- fix an ArrayIndexOutOfBoundsException issue when transforming native method
## 0.4.11
- support accessing private members of class under test in different package path
- validate the number of private method parameters accessed by `PrivateAccessor`
- fix a bug which may cause errors when the mock method contains array parameters
- fix an issue which cause some private members not be found in the IntelliJ build
## 0.4.10
- fix an issue of using mock in lambda expression
- fix the NullPointerException when invoke private method with parameter value `null`
## 0.4.9
- fix an issue cause by improperly bytecode processing while using `targetClass` parameter
- auto validate access target of `PrivateAccessor`, improve resistance to code refactoring
## 0.4.8
- fix an issue of private method invocation failed in assignment statements
- support using mock for testing with `SpringRunner`
- support `targetClass` parameter in `@MockMethod` annotation
## 0.4.7
- fix incorrect stack size caused by `MOCK_CONTEXT` variable initialization
- fix incorrect gradle build path of under Windows operating system
## 0.4.6
- fix an issue of `IINC` bytecode processing
- support `TestableTool.MOCK_CONTEXT` variable to inject extra parameters to mock context
- use `TestableTool.TEST_CASE` variable to distinguish test case is no longer recommended
## 0.4.5
- fix private access compile error in intelliJ 2020.3+ environment
- change javaagent initialization logic to avoid NPE in cross-layer test scenario
- support dump transformed byte code to local file for issue investigation
## 0.4.4
- fix an issue of accessing private method with interface type parameter fail
- fix an issue of mocking static method without parameter fail
@@ -22,7 +77,7 @@
- separate constant definition from `TestableTool` to `TestableConst`
## 0.3.2
- support grable project for both private member access and quick mock
- support gradle project for both private member access and quick mock
- support access private static field and methods via PrivateAccessor
## 0.3.1
@@ -39,12 +94,12 @@
## v0.2.1
- support mock static method
- support mock kotlin companion object method
- support mock invoke by interface / base class object
- support mock invoke by an interface / base class object
## v0.2.0
- use `TestableTool` class to expose test context and verify mock invoke
- add `testable-maven-plugin` module to simplify javaagent configuration
- remove dependence on EnableTestable annotation in `testable-agent`
- remove dependence on `@EnableTestable` annotation in `testable-agent`
- rename annotations to reflect the actual use
## v0.1.0

View File

@@ -0,0 +1,4 @@
Effective Scope Of Mocking
---
TO BE TRANSLATED

View File

@@ -3,9 +3,10 @@ Use TestableMock
`TestableMock` is an assist tool for Java unit testing based on source code and bytecode enhancement, including the following functions:
- [Quickly mock arbitrary call](en-us/doc/use-mock.md): quickly replace any method invocation in the class under test with a mock method, solve the cumbersome use of traditional mock tools problem
- [Access private members of the class under test](en-us/doc/private-accessor.md): enable unit tests directly invoke or access private members of the class under test, solve the problems of private member initialization and private method testing
- [Quick Mock arbitrary call](en-us/doc/use-mock.md): quickly replace any method invocation in the class under test with a mock method, solve the cumbersome use of traditional mock tools problem
- [Auxiliary test void method](en-us/doc/test-void-method.md): use the Mock validator to check the internal logic of method, solve the problem that unit testing is difficult to implement to the method with no return value
- [Quickly construct complicated parameter object](en-us/doc/omni-constructor.md)generate arbitrarily nested object instances, simplify their internal member assignment methods, solve the problem of long initialization codes for method parameters
- [Auxiliary test void method](en-us/doc/test-void-method.md): use the mock validator to check the internal logic of method, solve the problem that unit testing is difficult to implement to the method with no return value
## Use in Maven project
@@ -15,7 +16,7 @@ It is recommended to add a `property` field that identifies the TestableMock ver
```xml
<properties>
<testable.version>0.4.4</testable.version>
<testable.version>0.6.0</testable.version>
</properties>
```
@@ -62,12 +63,12 @@ Add dependence of `TestableMock` in `build.gradle` file:
```groovy
dependencies {
testImplementation('com.alibaba.testable:testable-all:0.4.4')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.4.4')
testImplementation('com.alibaba.testable:testable-all:0.6.0')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.6.0')
}
```
Then add javaagent to `test` configuration
Then add `javaagent` to "test" configuration
```groovy
test {
@@ -76,3 +77,19 @@ test {
```
See the [build.gradle](https://github.com/alibaba/testable-mock/blob/master/demo/java-demo/build.gradle) file of project `java-demo` and the [build.gradle.kts](https://github.com/alibaba/testable-mock/blob/master/demo/kotlin-demo/build.gradle.kts) file of project `kotlin-demo`.
> For Android project tested with `Robolectric` framework, please use the same method to add `TestableMock` dependency as above, and add `javaagent` configuration as follows:
>
> ```groovy
> android {
> testOptions {
> unitTests {
> all {
> jvmArgs "-javaagent:${classpath.find { it.name.contains("testable-agent") }.absolutePath}"
> }
> }
> }
> }
> ```
>
> See [issue-43](https://github.com/alibaba/testable-mock/issues/43) for a complete example.

View File

@@ -68,7 +68,7 @@ class Demo {
}
```
To test this method, you can use `TestableMock` to quickly mock out the `System.out.println` method. In the Mock method body, you can simply call the original method (equivalent to not affecting the original method function, only used for call recording), or leave it blank (equivalent to removing the side effects of the original method).
To test this method, you can use `TestableMock` to quickly mock out the `System.out.println` method. In the mock method body, you can simply call the original method (equivalent to not affecting the original method function, only used for call recording), or leave it blank (equivalent to removing the side effects of the original method).
After executing the void type method under test, use `InvokeVerifier.verify()` to verify whether the incoming print content meets expectations:
@@ -76,11 +76,13 @@ After executing the void type method under test, use `InvokeVerifier.verify()` t
class DemoTest {
private Demo demo = new Demo();
// Intercept `System.out.println` invocation
@MockMethod
public void println(PrintStream ps, String msg) {
// Execute the original call
ps.println(msg);
public static class Mock {
// Intercept `System.out.println` invocation
@MockMethod
public void println(PrintStream ps, String msg) {
// Execute the original call
ps.println(msg);
}
}
@Test

View File

@@ -3,38 +3,92 @@ Self-Help Troubleshooting
Compared with `Mockito` and other mock tools where developers have to manually inject mock classes, `TestableMock` uses method name and parameter type matching to automatically find invocations that require mock. While this mechanism brings convenience, it may also cause unexpected mock replacement.
To troubleshoot mock-related issues, just add the `@MockWith` annotation to the test class, and configure the parameter `diagnose` to `MockDiagnose.ENABLE`, so the detailed Mock method replacement process will be printed when the test is run.
To troubleshoot mock-related issues, just add the `@MockDiagnose` annotation to the mock class, and set the value `LogLevel.ENABLE`, so the detailed mock method replacement process will be printed when the test is run.
```java
@MockWith(diagnose = MockDiagnose.ENABLE)
class DemoTest {
...
@MockDiagnose(LogLevel.ENABLE)
public static class Mock {
...
}
}
```
The output log example is as follows:
```text
[DIAGNOSE] Handling test class com/alibaba/testable/demo/DemoMockTest
[DIAGNOSE] Handling source class com/alibaba/testable/demo/DemoMock
[DIAGNOSE] Found 7 mock methods
[DIAGNOSE] Handling test class com/alibaba/testable/demo/basic/DemoMockTest
[DIAGNOSE] Found 6 test cases
[DIAGNOSE] Handling mock class com/alibaba/testable/demo/basic/DemoMockTest$Mock
[DIAGNOSE] Found 8 mock methods
[DIAGNOSE] Handling source class com/alibaba/testable/demo/basic/DemoMock
[DIAGNOSE] Handling method <init>
[DIAGNOSE] Handling method newFunc
[DIAGNOSE] Line 14, mock method createBlackBox used
[DIAGNOSE] Line 19, mock method "createBlackBox" used
[DIAGNOSE] Handling method outerFunc
[DIAGNOSE] Line 22, mock method innerFunc used
[DIAGNOSE] Line 27, mock method "innerFunc" used
[DIAGNOSE] Line 27, mock method "staticFunc" used
[DIAGNOSE] Handling method commonFunc
[DIAGNOSE] Line 29, mock method trim used
[DIAGNOSE] Line 29, mock method sub used
[DIAGNOSE] Line 29, mock method startsWith used
[DIAGNOSE] Handling method getBox
[DIAGNOSE] Line 36, mock method secretBox used
[DIAGNOSE] Handling method callerOne
[DIAGNOSE] Line 43, mock method callFromDifferentMethod used
[DIAGNOSE] Handling method callerTwo
[DIAGNOSE] Line 47, mock method callFromDifferentMethod used
[DIAGNOSE] Handling method innerFunc
[DIAGNOSE] Handling method callFromDifferentMethod
[DIAGNOSE] Line 34, mock method "trim" used
[DIAGNOSE] Line 34, mock method "sub" used
[DIAGNOSE] Line 34, mock method "startsWith" used
... ...
```
The log shows all the mocked invocation and corresponding code line numbers in the class under test.
- Self troubleshooting:
- If there is no output, please check whether the `pom.xml` or `build.gradle` configuration correctly introduces `TestableMock` dependencies
- If only the first line of `Handling mock class` is output, please check whether the mock class is created at correct place
- If `Handling mock class` and `Handling test class` are output, please check whether the test class is in the same package of the class under test, and the name is "<ClassUnderTest>+Test", otherwise `@MockWith` annotation should be used
- If `Handling source class` and `Handling method xxx` are output, but there is no mock replacement happen at the expected code line, please check whether the mock method definition matches the target method
For situations where expected mocking is not take effect, you could set the diagnosis level to `LogLevel.VERBOSE` for further investigation information.
```java
class DemoTest {
@MockDiagnose(LogLevel.VERBOSE)
public static class Mock {
...
}
}
```
Executing the unit test again will print out the signatures of all mock methods, and the signatures of all invocations scanned in the class under test:
```text
[DIAGNOSE] Handling test class com/alibaba/testable/demo/basic/DemoMockTest
[VERBOSE] Test case "should_mock_new_object"
... ...
[VERBOSE] Test case "should_set_mock_context"
[DIAGNOSE] Found 6 test cases
[DIAGNOSE] Handling mock class com/alibaba/testable/demo/basic/DemoMockTest$Mock
[VERBOSE] Mock constructor "createBlackBox" as "com.alibaba.demo.basic.model.mock.BlackBox(java.lang.String)"
[VERBOSE] Mock method "innerFunc" as "com.alibaba.demo.basic.DemoMock::innerFunc(java.lang.String) : java.lang.String"
... ...
[VERBOSE] Mock method "callFromDifferentMethod" as "()Ljava/lang/String;"
[DIAGNOSE] Found 8 mock methods
[DIAGNOSE] Handling source class com/alibaba/testable/demo/basic/DemoMock
[DIAGNOSE] Handling method <init>
[VERBOSE] Line 13, constructing "java.lang.Object()"
[DIAGNOSE] Handling method newFunc
[VERBOSE] Line 19, constructing "com.alibaba.demo.basic.model.mock.BlackBox(java.lang.String)"
[DIAGNOSE] Line 19, mock method "createBlackBox" used
[VERBOSE] Line 19, invoking "com.alibaba.demo.basic.DemoMockTest$Mock::createBlackBox(java.lang.String) : com.alibaba.demo.basic.model.mock.BlackBox"
[VERBOSE] Line 20, invoking "com.alibaba.demo.basic.model.mock.BlackBox::get() : java.lang.String"
[DIAGNOSE] Handling method outerFunc
[VERBOSE] Line 27, constructing "java.lang.StringBuilder()"
[VERBOSE] Line 27, invoking "java.lang.StringBuilder::append(java.lang.String) : java.lang.StringBuilder"
[VERBOSE] Line 27, invoking "com.alibaba.demo.basic.DemoMock::innerFunc(java.lang.String) : java.lang.String"
[DIAGNOSE] Line 27, mock method "innerFunc" used
... ...
```
The logs are formatted in follow pattern:
- `Mock constructor "<MockMethodName>" as "<Signature>" for "<TypeName>"` Mock constructor found in test class
- `Mock method "<MockMethodName>" as "<Signature>"` Mock method found in test class (the first parameter that identify the mock target class is currently kept)
- `Line XX, constructing "<TypeName>" as "<Signature>"` Constructor invocation found in test under class
- `Line XX, invoking "<MethodName>" as "<Signature>"` Member method invocation found in test under class

View File

@@ -0,0 +1,67 @@
Upgrade Guide
---
### Upgrade to v0.6
In version `0.6``TestableMock` provided a new [quick complicated class construction](en-us/doc/omni-constructor.md) capability. Meanwhile, it includes a modification that is incompatible with the `0.5` version: class `PrivateAccessor` have been moved from `com.alibaba.testable.core.accessor` package to `com.alibaba.testable.core.tool` package.
If you are using `PrivateAccessor` and having import statement like this:
```java
import com.alibaba.testable.core.accessor.PrivateAccessor;
```
When upgrade `TestableMock` to `0.6` version, please replace it as
```java
import com.alibaba.testable.core.tool.PrivateAccessor;
```
### Upgrade to v0.5
The `0.5` version solves the three historical problems left over before:
1. <s>**Mock method cannot call other non-static methods**</s>. The mock method in the new version no longer has any difference from the ordinary method, and can access any external method and member variable.
2. <s>**Mock method always acts on the entire test life cycle**</s>. From now on, the mock method supports restricting the effective scope to the test cases in the test class of which it belongs, so there is no need to worry about accidentally mocking cross-class test invocations.
3. <s>**The MOCK_CONTEXT needs manually cleaned up and only supports class-level parallel testing**</s>. Now each test case has an independent `MOCK_CONTEXT` variable, no need to clean up after used, and you can use unit test with any parallel level.
In order to better realize the reuse of Mock methods, the version `0.5` have made a clear boundary between the mock class and the test class in the new version. When upgrading from `0.4` to `0.5`, the only change required is to wrap all mock methods in the test class with a `public static class Mock {}`.
For example, the original test class definition was as follows:
```java
public class DemoMockTest {
@MockMethod(targetClass = DemoMock.class)
private String innerFunc(String text) {
return "hello_" + text;
}
@Test
void should_mock_member_method() throws Exception {
assertEquals("hello_world", demoMock.outerFunc());
verify("innerFunc").with("world");
}
}
```
After upgrading to the `0.5` version, move all mock methods (in this example, only the `innerFunc` method) to a static inner class named `Mock`, which is equivalent to adding two lines of code:
```java
public class DemoMockTest {
public static class Mock { // Add this line
@MockMethod(targetClass = DemoMock.class)
private String innerFunc(String text) {
return "hello_" + text;
}
} // Add this line
@Test
void should_mock_member_method() throws Exception {
assertEquals("hello_world", demoMock.outerFunc());
verify("innerFunc").with("world");
}
}
```
Finally, upgrade the `TestableMock` dependency in `pom.xml` or `build.gradle` file to the new version.

View File

@@ -0,0 +1,18 @@
Use TestableMock In IDE
---
## Use IntelliJ IDE
IntelliJ IDE supports the `JSR-269` annotation processor and the `maven-surefire-plugin` arguments very well (both are techniques back the `TestableMock`). Usually you don't need any special configuration to make everything work, it's all out of the box.
## Use Eclipse IDE
Since the built-in compilation feature of `Eclipse` is based on a self-made compiler, it is not compatible with the standard `javac` compilation process, which will cause the `@EnablePrivateAccess` annotation to be invalid when running test cases in the IDE. However, the function of accessing the private members of the class under test through the `PrivateAccessor` tool class will not be affected by differences in the compiler.
If the `@EnablePrivateAccess` annotation is used in the project, you can use `mvn test -Dtest=<TestClassName>` and `mvn test -Dtest=<TestClassName>#<TestCaseName>` in the command line of `Eclipse` to run a single test class or test case.
At the same time, because the built-in unit test executor of `Eclipse` completely ignores the configuration of the `pom.xml` file, additional configuration is required to use the Mock function.
Take the use of `JUnit` as an example. You need to pull down from the small triangle next to the run button on the IDE toolbar, select "Run Configurations...", select the task to run the unit test on the left side, and switch to "arguments" Tab on the right side, append a `-javaagent:` parameter in the "VM Options", the following figure is an example, note that the `testable-agent` package should be modified to match the actual situation of the local Maven repository path.
![eclipse-junit-configuration](https://testable-code.oss-cn-beijing.aliyuncs.com/eclipse-junit-configuration.png)

View File

@@ -0,0 +1,4 @@
Use MockWith Annotation
---
TO BE TRANSLATED

167
docs/en-us/doc/use-mock.md Normal file
View File

@@ -0,0 +1,167 @@
Fast Mocking
---
Compared with the class-granularity mocking practices of existing mock tools, `TestableMock` allows developers to directly define a single method and use it for mocking. With the principle of convention over configuration, mock method replacement will automatically happen when the specified method in the test class match an invocation in the class under test.
> In summary, there are two simple rules:
> - Mock non-constructive method, copy the original method definition to the mock class, add a `@MockMethod` annotation
> - Mock construction method, copy the original method definition to the mock class, replace the return value with the constructed type, the method name is arbitrary, and add a `@MockContructor` annotation
The detail mock method definition convention is as follows.
### 0. Pre-step, prepare the mock class
First, create a mock class as the container for mock methods associated with the test class. The simplest way is to add a static inner class named `Mock` to the test class. E.g:
```java
public class DemoTest {
public static class Mock {
// mock methods goes here
}
}
```
### 1.1 Mock method calls of any class
Define an ordinary method annotated with `@MockMethod` in the mock class with exactly the same signature (name, parameter, and return value type) as the method to be mocked, and then add the type of target object (which the method originally belongs to) as `targetMethod` parameter of `@MockMethod` annotation.
At this time, all invocations to that original method in the class under test will be automatically replaced with invocations to the above-mentioned mock method when the unit test is running.
For example, there is a call to `"anything".substring(1, 2)` in the class under test, and we want to change it to a fixed string when running the test, we only need to define the following method in the mock class:
```java
// The original method signature is `String substring(int, int)`
// The object `"anything"` that invokes this method is of type `String`
@MockMethod(targetClass = String.class)
private String substring(int i, int j) {
return "sub_string";
}
```
When several methods to be mocked have the same name, you can put the name of the method to be mocked in the `targetMethod` parameter of `@MockMethod` annotation, so that the mock method itself can be named at will.
The following example shows the usage of the `targetMethod` parameter, and its effect is the same as the above example:
```java
// Use `targetMethod` to specify the name of the method that needs to be mocked
// The method itself can now be named arbitrarily, but the method parameters still need to follow the same matching rules
@MockMethod(targetClass = String.class, targetMethod = "substring")
private String use_any_mock_method_name(int i, int j) {
return "sub_string";
}
```
Sometimes, the mock method need to access the member variables in the original object that initiated the invocation, or invoke other methods of the original object. At this point, you can remove the `targetClass` parameter in the `@MockMethod` annotation, and then add a extra parameter whose type is the original object type of the method to the first index of the method parameter list.
The `TestableMock` convention is that when the `targetClass` parameter value of the `@MockMethod` annotation is empty, the first parameter of the mock method is the type of the target method, and the parameter name is arbitrary. In order to facilitate code reading, it is recommended to name this parameter as `self` or `src`. Example as follows:
```java
// Adds a `String` type parameter to the first position the mock method parameter list (parameter name is arbitrary)
// This parameter can be used to get the value and context of the actual invoker at runtime
@MockMethod
private String substring(String self, int i, int j) {
// Call the original method is also allowed
return self.substring(i, j);
}
```
For complete code examples, see the `should_mock_common_method()` test cases in the `java-demo` and `kotlin-demo` sample projects. (Because Kotlin has made magical changes to the String type, the method under test in the Kotlin example adds a layer of encapsulation to the `BlackBox` class)
### 1.2 Mock the member method of the class under test itself
Sometimes, when testing certain methods, it is desirable to mock out some other member methods of the class under test itself.
The solution is the same as the previous case. Just set `targetClass` parameter value to the type of class under test.
For example, there is a private method with the signature `String innerFunc(String)` in the class under test. If we want to replace it during testing, we only need to define the following method in the mock class:
```java
// The type to test is `DemoMock`
@MockMethod(targetClass = DemoMock.class)
private String innerFunc(String text) {
return "mock_" + text;
}
```
Similarly, if the method in the above example needs to access the original tested object that initiated the call, it may not use the `targetClass` parameter, but when defining the mock method, add a parameter of type `DemoMock` to the first index of the method parameter list.
For complete code examples, see the `should_mock_member_method()` test case in the `java-demo` and `kotlin-demo` sample projects.
### 1.3 Mock static methods of any class
Mock for static methods is the same as for any ordinary methods.
For example, if the static method `secretBox()` of the `BlackBox` type is invoked in the class under test, and the method signature is `BlackBox secretBox()`, then the mock method is as follows:
```java
@MockMethod(targetClass = BlackBox.class)
private BlackBox secretBox() {
return new BlackBox("not_secret_box");
}
```
For complete code examples, see the `should_mock_static_method()` test case in the `java-demo` and `kotlin-demo` sample projects.
### 1.4 Mock `new` operation of any type
Define an ordinary method annotated with `@MockContructor` in the mock class, make the return value type of the method the type of the object to be created, and the method parameters are exactly the same as the constructor parameters to be mocked, the method name is arbitrary.
At this time, all operations in the class under test that use `new` to create the specified class (and use the constructor that is consistent with the mock method parameters) will be replaced with calls to the custom method.
For example, if there is a call to `new BlackBox("something")` in the class under test, and you want to change it during unit testing (usually by replacing it with a mock object, or by replacing it with a temporary object created with test parameters), just define the following mock method:
```java
// The signature of the constructor to be mocked is `BlackBox(String)`
// No need to add additional parameters to the mock method parameter list, and the name of the mock method is arbitrary
@MockContructor
private BlackBox createBlackBox(String text) {
return new BlackBox("mock_" + text);
}
```
> You can still use the `@MockMethod` annotation, and configure the `targetMethod` parameter value to `"<init>"`, and the rest is the same as above. The effect is the same as using the `@MockContructor` annotation
For complete code examples, see the `should_mock_new_object()` test case in the `java-demo` and `kotlin-demo` sample projects.
### 2. Identify different invocation source in mock method
In the mock method, you can use the `TestableTool.SOURCE_METHOD` variable to identify **the method name of the class under test before entering the mock method**; in addition, the `TestableTool.MOCK_CONTEXT` variable can **inject additional context parameters into the mock method**, to distinguish and process different calling scenarios.
For example, to verify the impact on the target method under test when the mock method returns different results in the test case:
```java
@Test
public void testDemo() {
MOCK_CONTEXT.set("case", "data-ready");
assertEquals(true, demo());
MOCK_CONTEXT.set("case", "has-error");
assertEquals(false, demo());
}
```
Take out the injected parameters in the mock method and return different results according to the situation:
```java
@MockMethod
private Data mockDemo() {
switch((String)MOCK_CONTEXT.get("case")) {
case "data-ready":
return new Data();
case "has-error":
throw new NetworkException();
default:
return null;
}
}
```
For complete code examples, see the `should_get_source_method_name()` and `should_get_test_case_name()` test cases in the `java-demo` and `kotlin-demo` sample projects.
### 3. Verify the sequence and parameters of the mock method being invoked
In test cases, you can use the `TestableTool.verify()` method, and cooperate with `with()`, `withInOrder()`, `without()`, `withTimes()` and other methods to verify the mock call situation.
For details, please refer to the [Check Mock Call](en-us/doc/matcher.md) document.

View File

@@ -1,16 +1,26 @@
- Quick Start
- [Use TestableMock](en-us/doc/setup.md)
- [Private Accessor](en-us/doc/private-accessor.md)
- [Fast Mocking](en-us/doc/use-mock.md)
- [Private Accessor](en-us/doc/private-accessor.md)
- [Omni Constructor](en-us/doc/omni-constructor.md)
- [Test Void Method](en-us/doc/test-void-method.md)
- Usage Reference
- Usage Guide
- [Verify Mock Invocation](en-us/doc/invoke-matcher.md)
- [Effective Scope Of Mocking](zh-cn/doc/scope-of-mock.md)
- [Reusing Mock Methods](zh-cn/doc/mock-method-reusing.md)
- [Use MockWith Annotation](zh-cn/doc/use-mock-with.md)
- [Frequently Asked Questions](en-us/doc/frequently-asked-questions.md)
- [Use TestableMock In IDE](en-us/doc/use-in-ide.md)
- [Self-Help Troubleshooting](en-us/doc/troubleshooting.md)
- [Testable Maven Plugin](en-us/doc/use-maven-plugin.md)
- Technical Reference
- [Upgrade Guide](en-us/doc/upgrade-guide.md)
- [Available Global Arguments](zh-cn/doc/javaagent-args.md)
- [Available Annotations](zh-cn/doc/annotations.md)
- [How Mock Works](zh-cn/doc/design-and-mechanism.md)
- [Mock Tools Comparison](en-us/doc/comparation.md)
- [Release Note](en-us/doc/release-note.md)
- [About Us](en-us/doc/about-us.md)
- [Feedback Channel](en-us/doc/feedback.md)

View File

@@ -26,7 +26,7 @@
hook.beforeEach(function (html) {
return html
+ '\n\n----\n\n'
+ '<a href="https://devops.aliyun.com" target="_blank" style="color: inherit; font-weight: normal; text-decoration: none;">Powered by 云效</a>'
+ '<a href="https://www.aliyun.com/product/yunxiao" target="_blank" style="color: inherit; font-weight: normal; text-decoration: none;">Powered by 云效</a>'
})
}
]

View File

@@ -1,11 +1,11 @@
TestableMock简介
---
单元测试中的Mock方法通常是为了绕开那些依赖外部资源或无关功能的方法调用使得测试重点能够集中在需要验证和保障的代码逻辑上。
单元测试中的Mock方法通常是为了绕开那些依赖外部资源或无关功能的方法调用使得测试重点能够集中在需要验证和保障的代码逻辑上。某个调用需要被Mock往往只与其自身特征有关而与调用的来源无关。
在定义Mock方法时开发者真正关心的只有一件事"<u>这个调用在测试的时候要换成那个假的Mock方法</u>"。
然而当下主流的Mock框架在实现Mock功能时需要开发者操心的事情实在太多Mock框架如何初始化、与所用的单元测试框架是否兼容、要被Mock的方法是不是私有的、是不是静态的、被Mock对象是new出来的还是注入的、怎样把被测对象送回被测类里...这些非关键的额外工作极大分散了使用Mock工具应有的乐趣。
当下主流的Mock框架在实现Mock功能时需要开发者操心的事情实在太多Mock框架如何初始化、与所用的服务框架是否兼容、要被Mock的方法是不是私有的、是不是静态的、被Mock对象是new出来的还是注入的、怎样把被测对象送回被测类里...这些非关键的额外工作极大分散了使用Mock工具应有的乐趣。
于是,我们开发了`TestableMock`**一款特立独行的轻量Mock工具**。

View File

@@ -0,0 +1,9 @@
## 关于我们
`TestableMock`的核心开发团队来自**阿里云·云研发部**(前身为**阿里集团研发效能部**)。我们不仅对内支持阿里数万名技术小二每天从任务分配、代码提交、功能测试、到产品发布的全生命周期流程,同时也是阿里云企业级研发协同平台[云效](https://www.aliyun.com/product/yunxiao)的产品研发团队,一直深耕在开发者工具领域,致力于改善开发者生产体验和构筑企业数字化研发生态。
这个项目的原始灵感来自于我们对Java开发者在日常单元测试中经常遇到的痛点总结[外部依赖Mock繁琐](zh-cn/doc/use-mock.md)、[私有方法难测试](zh-cn/doc/private-accessor.md)、[无返回值方法难测试](zh-cn/doc/test-void-method.md)、[复杂参数难构造](zh-cn/doc/parameter-constructor.md))。它在内部的名字是`Testable`,开源时为了搜索引擎优化,改名为`TestableMock`并主推轻量级Mock功能。 不要被它的名字所迷惑,`TestableMock`绝不仅仅是Mock。
从黑客马拉松孵化,到内部开源,再到对外开源,`TestableMock`已经积累了一批阿里集团内部和外部社区的开发者用户。与此同时,我们也在不断完善`TestableMock`自身的功能丰富性和稳定性。按照当前的版本发布计划,每次新版本发布前会首先提交包含`SNAPSHOT`标记的内测版到内部仓库在至少稳定使用一天以后才会成为正式版提交到Maven中心仓库紧急BUG修复版本除外以确保工具在为大家带来更多便捷的同时具有足够的可靠性。
最后,关于社区里有对“破坏封装进行单元测试是否等同于纵容代码腐化”的讨论,我们持相对乐观的态度。正如`PowerMock`的诞生并没有真的催生更多烂项目反而切实解决了许多Java语言遗留的测试难题。与其束手束脚的采用“测试技巧”间接测试功能不如索性解除限制就让单元测试来得更猛烈一些吧🤠

View File

@@ -0,0 +1,60 @@
注解参数清单
---
基于轻量的原则,`TestableMock`为开发者提供了尽可能精炼、易用的注解组合,以下参数信息可供开发参考。
#### @EnablePrivateAccess
启用对被测类的<u>私有成员访问编译期增强</u>和<u>私有目标存在性的编译期校验</u>功能。
- 作用于:测试类
| 参数 | 类型 | 是否必须 | 默认值 | 作用 |
| --- | --- | --- | ---- | --- |
| srcClass | Class | 否 | N/A | 当测试类命名不符合约定时,指定实际被测类 |
| verifyTargetOnCompile | boolean | 否 | true | 是否启用私有目标的编译期存在性校验 |
#### @MockMethod
将当前方法标识为待匹配的Mock成员方法。
- 作用于Mock容器类中的方法
| 参数 | 类型 | 是否必须 | 默认值 | 作用 |
| --- | --- | --- | ---- | --- |
| targetClass | Class | 否 | N/A | 指定Mock目标的调用者类型 |
| targetMethod | String | 否 | N/A | 指定Mock目标的方法名 |
| scope | MockScope | 否 | MockScope.GLOBAL | 指定Mock的生效范围 |
#### @MockConstructor
将当前方法标识为待匹配的Mock构造方法。
- 作用于Mock容器类中的方法
| 参数 | 类型 | 是否必须 | 默认值 | 作用 |
| --- | --- | --- | ---- | --- |
| scope | MockScope | 否 | MockScope.GLOBAL | 指定Mock的生效范围 |
#### @MockWith
显式指定当前类型关联的Mock容器类。
- 作用于:测试类、被测类
| 参数(`N/A`为默认参数) | 类型 | 是否必须 | 默认值 | 作用 |
| --- | --- | --- | ---- | --- |
| N/A | Class | 否 | NullType.class | 指定使用的Mock容器类 |
| treatAs | ClassType | 否 | ClassType.GuessByName | 指定当前类是测试类或被测类 |
#### @MockDiagnose
启用或禁止Mock相关的诊断信息输出。
- 作用于Mock容器类
| 参数(`N/A`为默认参数) | 类型 | 是否必须 | 默认值 | 作用 |
| --- | --- | --- | ---- | --- |
| N/A | LogLevel | 是 | N/A | 指定当前Mock容器关联测试用例的诊断日志级别 |

View File

@@ -21,6 +21,6 @@
相比之下,`TestabledMock`的功能与`PowerMock`基本平齐,且极易上手,只需掌握`@MockMethod`注解就可以完成绝大多数任务。
当前`TestableMock`的主要不足在于编写Mock方法时IDE无法即时提示方法参数是否正确匹配。若发现匹配效果不符合预期,需要通过[自助问题排查](zh-cn/doc/troubleshooting.md)文档提供的方法在运行期进行校验。这个功能未来需要通过扩展主流IDE插件来提供
当前`TestableMock`的主要不足在于编写Mock方法时IDE无法即时提示方法参数是否正确匹配。若发现匹配效果不符合预期需要通过[自助问题排查](zh-cn/doc/troubleshooting.md)文档提供的方法在运行期进行校验。这个功能理论上能够通过扩展主流IDE插件来补充,但目前暂无相关开发计划,参见[Issue-104](https://github.com/alibaba/testable-mock/issues/104)
此外,由于`TestableMock`独辟蹊径的采用基于单个方法的Mock机制将Mock方法定义与单元测试用例解耦一方面使得Mock方法具有默认可复用性单元测试用例也因此变得更干净纯粹另一方面也导致Mock方法定义变得零散生命周期管理起来相对困难对现有开发者的Mock编写习惯会带来一定改变。
此外,由于`TestableMock`独辟蹊径的采用“每个业务类拥有一个专属Mock容器类”的思维方式将Mock方法定义与单元测试用例解耦一方面使得Mock方法具有默认可复用性单元测试用例也因此变得更干净纯粹另一方面也导致Mock方法定义变得零散生命周期管理起来相对困难对现有开发者的Mock编写习惯会带来一定改变。

View File

@@ -0,0 +1,29 @@
TestableMock的设计和原理
---
这篇文档主要介绍`TestableMock`中Mock功能的设计思想和实现原理。
与常见的Mock工具在每个测试用例里写Mock定义不同`TestableMock`让每个业务类直接提供自己的Mock方法集合描述自身在测试时需要被Mock的调用以及相应替代逻辑即每个业务类有自己的独立Test类和独立Mock类。采用约定优于配置降低Mock学习理解成本、减少冗余信息。
这种设计基于两项基本假设:
1. 同一个测试类里一个测试用例里需要Mock掉的方法在其他测试用例里通常也都需要Mock。因为这些被Mock的方法往往访问了不便于测试的外部依赖。
2. 需要Mock的调用都来自被测类的代码。此假设是符合单元测试初衷的即单元测试只应该关注当前单元的内部行为单元外的逻辑应该被替换为Mock
据此通过约定来简化符合该假设的单元测试场景,通过配置来支持其余复杂的使用场景。
`TestableMock`的原理可以用一句话概括:<u>利用JavaAgent动态修改字节码把被测的业务类中与所有与Mock方法定义匹配的调用在单元测试运行时替换成对Mock方法的调用</u>。
最终达到的效果则是不论代码用什么服务框架、什么对象容器不论要Mock的目标对象是注入的、new出来的、全局的还是局部的不论要Mock的目标方法是私有的、外部的、静态的、继承来的或者重载过的全部无差别通吃让单元测试回归简单。
> 划重点Mock的目标是**被测类**中的**方法调用**。测试用例里的代码不会被Mock方法的定义本身没有变化只是发起调用的代码被替换了。
具体来说,在单元测试启动时,`TestableMock`会对加载到内存中的类进行预处理同时分别建立“被测类”、“测试类”、“Mock容器类”之间的关联关系可以是一对一也可以是多对一。这个关联一方面是为了在测试用例执行时能够正确匹配Mock调用并进行替换另一方面则是为了能控制Mock方法的生效范围。
对于被测类将匹配到的调用换成对Mock容器方法的调用。
对于测试类在每个测试用例开头插入Mock上下文初始化代码。
对于Mock容器类增加`testableIns()`方法变成单例类在每个Mock方法开头插入记录调用的代码。
以上是整个Mock的核心逻辑更多实现细节请参考源码。若有任何问题、建议、改进提议都欢迎通过Github Issue和Pull Request参与讨论、贡献😃

View File

@@ -1,13 +1,17 @@
常见使用问题
---
#### 1. 如何Mock被测类中通过`@Autowired`初始化的字段?
#### 1. 如何初始化被测类中通过`@Autowired`或`@Resource`注入的私有字段?
直接创建被测类对象,然后利用`TestableMock`访问私有成员的能力直接给这些字段赋值即可
若该对象的方法在测试时需要被Mock则无需初始化
若测试运行时需用到该对象的真实调用,则可以在测试类的构造方法内直接创建对象,然后利用`TestableMock`访问私有成员的能力给这些字段赋值。
对于JUnit框架还可以使用`@RunWith(SpringRunner.class)`注解将Spring上下文启动起来然后在测试类里用`@Resource`把需要依赖对象注入进来,再利用`PrivateAccessor`赋值给被测类的私有字段。
#### 2. `TestableMock`是否能够与其他Mock工具一起使用
`TestableMock`可与其他基于动态代理机制的Mock工具安全的共同使用譬如`Mockito``EasyMock``MockRunner`等皆属此范畴。
`TestableMock`可与其他基于动态代理机制的Mock工具安全的共同使用譬如`Mockito``Spock``EasyMock`等皆属此范畴。
对于会修改类加载器或被测类字节码的Mock工具譬如`PowerMock``JMockit`,尚无案例证明会与`TestableMock`发生冲突,但从原理来说二者可能存在不兼容风险,请谨慎使用。
@@ -29,20 +33,26 @@
> 由于JVM存在泛型擦除机制对于Java项目也可以直接使用`Object`类型替代泛型参数见Java版`DemoTemplateTest`测试类中被注释掉的"第二种写法"示例。
#### 5. 在Kotlin项目对`String`类中的方法进行Mock不生效
#### 5. 如何Mock在内部类代码里的调用
在其所在外部类对应的Mock容器中定义所需的Mock方法即可。
参见Java和Kotlin示例中`DemoInnerClass`测试类的用例。
#### 6. 在Kotlin项目对`String`类中的方法进行Mock不生效
Kotlin语言中的`String`类型实际上是`kotlin.String`,而非`java.lang.String`。但在构建生成自字节码的时候又会被替换为Java的`java.lang.String`因此无论将Mock目标写为`kotlin.String``java.lang.String`均无法正常匹配到原始的被调用方法。
实际场景中需要对`String`类中的方法进行Mock的场景很少`TestableMock`暂未对这种情况做特别处理。
#### 6. 在IntelliJ IDE中运行单个测试用例时用了`@EnablePrivateAccess`注解还是报私有成员访问错误?
IntelliJ默认编译方法对`JSR-269`规范注解处理器的处理机制与Maven标准不完全兼容在IntelliJ系统配置的"Build Tools > Maven > Runner"中开启"Delegate IDE build/run actions to maven"选项即可:
![delegate-ide-build-to-maven](https://testable-code.oss-cn-beijing.aliyuncs.com/delegate-ide-build-to-maven.png)
#### 7. `TestableMock`能否用于Android项目的测试
结合[Roboelectric](https://github.com/robolectric/robolectric)测试框架可使用。
Android系统的`Dalvik``ART`虚拟机采用了与标准JVM不同的字节码体系会影响`TestableMock`的正常工作。`Roboelectric`框架能在普通JVM虚拟机上运行Android单元测试其速度比通过Android虚拟机运行单元测试快非常多绝大多数Android App的单元测试都在使用`Roboelectric`框架。
#### 8. 在IntelliJ运行测试报"Command Line is too Long. Shorten command line for ..."错误?
这个问题是由于系统ClassPath包含太多路径所致与是否使用`TestableMock`无关。但需要注意的是IntelliJ提供了两种辅助解决机制`JAR manifest``classpath file`,若测试中使用了`TestableMock`,请选择`JAR manifest`
![jar-manifest](https://testable-code.oss-cn-beijing.aliyuncs.com/jar-manifest.png)

View File

@@ -0,0 +1,45 @@
全局运行参数
---
`TestableMock`的许多功能采用了基于JavaAgent的运行时字节码修改实现。在JavaAgent启动时可以通过额外的全局参数来调整`TestableMock`的执行过程。
### 可用参数清单
| 参数 | 描述 | 可用值和示例(`N/A`表示无需赋值) |
| ---- | ---- | ---- |
| logLevel | 修改全局日志级别 | 可用值为:`mute`(禁止打印警告) / `debug`(打印调试信息) / `verbose`(打印非常详细的调试信息) |
| dumpPath | 将修改过后的字节码保存到本地指定目录(用于排查问题) | 例如 `/tmp/bytecode`(需要此目录事先存在) |
| pkgPrefix | 限定`TestableMock`仅对部分包生效 | 使用`,`分隔的包路径前缀列表,例如 `com.demo.svc,com.demo.dao` |
| mockScope | 修改默认的Mock生效范围详见[Mock生效范围](zh-cn/doc/scope-of-mock) | 可用值为:`global`(全局生效) / `associated`(只对关联的测试用例生效) |
| useThreadPool | 启用基于`TransmittableThreadLocal`的Mock上下文存储用于包含线程池的测试用例 | `N/A` |
### 参数的连接
若参数有值,参数名和值之间用`=`符合连接。例如:
`useThreadPool``logLevel=debug``dumpPath=/tmp/debug`
多个参数之间使用`&`符号连接,例如:
`useThreadPool&logLevel=debug``logLevel=debug&dumpPath=/tmp/debug`
### 添加运行参数
与其他基于JavaAgent的工具相似`TestableMock`通过在单元测试引入`testable-agent`包的末尾加上一个`=`符号,然后连接额外参数来传递用户的自定义参数。
对于Maven项目可将参数追加到`maven-surefire-plugin`参数`testable-agent`包尾部,紧接着`.jar`的位置。例如:
```xml
<configuration>
<argLine>-javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar=mockScope=associated&amp;pkgPrefix=com.demo.</argLine>
</configuration>
```
> 注意:在`xml`文件中,连接参数的`&`符号需要写为`&amp;`
对于Gradle项目同样是直接将参数追加到引入`testable-agent`的配置末尾。例如:
```groovy
jvmArgs "-javaagent:${classpath.find { it.name.contains("testable-agent") }.absolutePath}=mockScope=associated&pkgPrefix=com.demo."
```

View File

@@ -0,0 +1,63 @@
复用Mock类与方法
---
Don't Repeat Yourself是软件开发过程当中的一项重要原则即“DRY原则”在编写测试代码时有些通用的基础功能调用语句常常出现在许多相似的业务类里若测试中需要Mock这些调用就要在各个测试类中重复提供同样的Mock方法。而通过Mock方法的复用机制能够很好的避免编写臃肿重复Mock代码的麻烦。
TestableMock支持两种粒度的Mock复用方式<u>复用Mock类</u>和<u>复用Mock方法</u>。
## 复用Mock类
如果有两个或以上测试类需要Mock的方法近乎相同那么采用类级别的Mock复用就是最省心的一种方式。
进行类级别的Mock复用只需将Mock容器定义为独立的类然后在要使用它的测试类上通过`@MockWith`进行引用。例如:
```java
@MockWith(ServiceMock.class)
public class AaaServiceTest {
...
}
@MockWith(ServiceMock.class)
public class BbbServiceTest {
...
}
public class ServiceMock {
...
}
```
这样在`AaaServiceTest``BbbServiceTest`类中的测试用例在执行时,都会用`ServiceMock`容器类中定义的Mock方法进行调用匹配和Mock替换。
## 复用Mock方法
实际场景中相比一次性复用整个Mock类的情况更常见的是对部分高频Mock方法进行复用。
Mock方法的复用可以通过Mock容器类的继承来实现父类中定义的所有Mock方法都会在子类中自然存在例如
```java
public class AaaServiceTest {
public static class Mock extends BasicMock {
...
}
...
}
public class BbbServiceTest {
public static class Mock extends BasicMock {
...
}
...
}
public class BasicMock {
@MockMethod(targetClass = UserDao.class)
protected String getById(int id) {
...
}
}
```
则名为`getById`的Mock方法在`AaaServiceTest``BbbServiceTest`的测试用例执行时都会生效。

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