Compare commits

...

338 Commits

Author SHA1 Message Date
金戟
5904ad99cb release v0.6.2 2021-04-07 21:38:24 +08:00
金戟
74fbc5b53f support configure file 2021-04-07 09:40:20 +08:00
金戟
f6b5bab250 manual generated serialVersionUID not works 2021-04-07 00:05:29 +08:00
金戟
f2feb3cc68 more precise omni handler filter 2021-04-06 23:08:30 +08:00
金戟
b41923bfa0 release v0.6.1 2021-04-06 08:09:23 +08:00
金戟
7c1f18de46 optimize docs 2021-04-06 00:03:38 +08:00
金戟
c393d40d0d better log description, handling -> found 2021-04-05 15:52:12 +08:00
金戟
d4a69528a7 use relative path for bytecode dumping 2021-04-05 14:58:29 +08:00
金戟
a46e73a421 support specify log file path 2021-04-05 14:09:44 +08:00
金戟
01e9890234 always generate log file 2021-04-05 11:56:06 +08:00
金戟
93cdef1d6c remove gradle settings in kotlin demo 2021-04-04 15:38:43 +08:00
金戟
e2e986e50d add a tiny demo for spock 2021-04-04 15:31:18 +08:00
金戟
f5475aa18e simplify log level config 2021-04-04 00:10:56 +08:00
金戟
844323af3a fix compatibility with spring boot 2021-04-03 23:54:01 +08:00
金戟
cc2deeff1c ClassCircularityError is not an exception 2021-04-03 23:35:22 +08:00
金戟
14e4d2db5b ignore cglib classes 2021-04-03 22:51:28 +08:00
金戟
c9c8ba6c24 junit test class can also has parent 2021-04-03 22:10:18 +08:00
金戟
b5c0afb3da ignore any exception when try load class 2021-04-03 18:17:11 +08:00
金戟
617d372545 avoid to transfer gradle TestWorker class 2021-04-03 18:16:36 +08:00
金戟
58da25de53 add mock verifier support spock 2021-04-03 13:41:51 +08:00
金戟
46706ba56c add supported test framwork 2021-04-03 10:02:03 +08:00
金戟
1ced04bf29 fix loging 2021-04-02 23:50:39 +08:00
金戟
7b2f967626 add tool scripts 2021-04-01 09:22:28 +08:00
金戟
e0a4a39d55 move dump method to util package 2021-03-31 10:34:45 +08:00
金戟
b4da23a90e adopt for groovy bytecode 2021-03-31 10:34:19 +08:00
金戟
c1659242b6 fix markdown format 2021-03-28 15:10:52 +08:00
金戟
929abffb66 add english doc for omni methods 2021-03-28 14:47:00 +08:00
金戟
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
金戟
d6ac715f57 release v0.4.4 2020-12-24 07:33:45 +08:00
金戟
6b126223e8 fix mocking bug of static method without parameter 2020-12-23 21:12:40 +08:00
金戟
508610b0c8 test scope is actually enough 2020-12-23 20:23:09 +08:00
金戟
459fea37c3 more english docs 2020-12-23 20:19:15 +08:00
金戟
703aa319ba type assignable instead of equal 2020-12-23 18:53:26 +08:00
金戟
df7deedcce update translated english doc 2020-12-23 14:21:02 +08:00
金戟
4701b3b42b fix doc according to recent feedbacks 2020-12-23 10:19:42 +08:00
金戟
085c6784d4 add ali-tech link and english readme 2020-12-23 08:45:53 +08:00
Fan Lin
a1437e7291 Update README.md
add gitee doc link
2020-12-22 14:54:08 +08:00
金戟
d17bdb258b release v0.4.3 2020-12-21 10:27:46 +08:00
金戟
03253e529d handle static member access 2020-12-20 21:30:34 +08:00
金戟
6b4467a03b update introduction 2020-12-20 19:02:28 +08:00
金戟
8cfaa6d36e add one more faq and update doc images 2020-12-19 22:43:51 +08:00
金戟
78a19b6876 add feedback doc 2020-12-19 17:48:35 +08:00
金戟
5484b216f1 merge maven plugin configure 2020-12-17 16:07:13 +08:00
金戟
d1007f6166 support mute agent log 2020-12-17 13:17:56 +08:00
金戟
9b82fcdc24 fix incorrect matches invoke in doc 2020-12-17 11:55:16 +08:00
金戟
bf4c263d93 bump to v0.4.2 2020-12-15 19:19:58 +08:00
金戟
28bde0b85a method invocation could contain LabelNode and LineNumberNode 2020-12-15 14:19:05 +08:00
金戟
179e71c7c2 loaded class cache not actually working 2020-12-14 22:57:13 +08:00
金戟
f624c9131e fix null pointer exception 2020-12-14 22:45:41 +08:00
金戟
83974cfb2a avoid duplicate test class injection 2020-12-14 22:36:35 +08:00
金戟
abb6d04c16 support set log level via maven plugin 2020-12-14 21:25:51 +08:00
金戟
16ec3b9444 also add drawbacks 2020-12-14 19:13:12 +08:00
金戟
180dfe9da8 set to next snapshot 2020-12-14 19:06:38 +08:00
金戟
92a2c69e05 replace trump with simpson 2020-12-12 10:36:42 +08:00
金戟
f40834eb94 add comparation doc 2020-12-12 08:42:08 +08:00
金戟
2dea6f9480 update docs and bump version to 0.4.1 2020-12-12 00:05:53 +08:00
金戟
2bdad3c1f6 separete description of PrivateAccessor from @EnablePrivateAccess 2020-12-11 21:36:30 +08:00
金戟
3f672e45da do deprecate instead of removal 2020-12-11 21:19:16 +08:00
金戟
328c8540a8 use MockMethod and MockConstructor replace TestableMock annotation 2020-12-11 20:29:49 +08:00
金戟
857b5e2295 bump to v0.4.0 2020-12-11 12:22:22 +08:00
金戟
1ec2c48c4b releasing config needed in parent pom 2020-12-10 20:09:27 +08:00
金戟
0ae39d9deb green light the demo package 2020-12-10 20:02:23 +08:00
金戟
dad2ea3a76 update log level when default log level changed 2020-12-10 19:22:22 +08:00
金戟
260fe94a49 fix a classloader filter issue for java 9+ 2020-12-10 19:21:33 +08:00
金戟
dfb1e2c094 revert maven plugin pom change 2020-12-10 16:11:14 +08:00
金戟
c8115f9623 add verbose log when test start 2020-12-10 15:47:55 +08:00
金戟
4def468d85 suggest invoking original method 2020-12-10 09:17:44 +08:00
金戟
efe57feeda maven plugin will not follow the parent version 2020-12-10 08:54:39 +08:00
金戟
5150868e8c move CONSTRUCTOR to TestableConst 2020-12-10 08:45:01 +08:00
金戟
757becc08b update doc about generics type method and add kotlin demo 2020-12-09 23:37:26 +08:00
金戟
857cbac81c add demo for template method mocking 2020-12-09 20:03:19 +08:00
金戟
495c08f716 extra common configure to parent pom 2020-12-08 16:22:14 +08:00
金戟
da650eb14d add testable all module 2020-12-08 15:51:51 +08:00
金戟
bd0ec5f4b7 reduce log reset frequency 2020-12-07 23:26:18 +08:00
金戟
65807eece7 avoid test case naming conflict between classes 2020-12-05 10:16:22 +08:00
金戟
b74b21cae6 set to snapshot 2020-12-05 09:39:20 +08:00
金戟
ad63de628d refactor log methods 2020-12-05 09:29:14 +08:00
金戟
08aa73b4bd use global log level 2020-12-04 22:50:02 +08:00
金戟
dd7499f3cf bump version to 0.3.2 2020-12-01 23:54:04 +08:00
金戟
1d98e3c618 support invoke private companion object method in kotlin 2020-12-01 23:49:48 +08:00
金戟
001001bfd3 fully support gradle build and test 2020-12-01 20:20:44 +08:00
金戟
472f90e7e4 add static access methods to private accessor 2020-11-30 22:57:37 +08:00
金戟
1af6b8c8b6 fit private access for gradle build 2020-11-30 21:32:13 +08:00
金戟
1bf6ab3b21 fit test case name fetcher for gradle build 2020-11-29 10:39:55 +08:00
金戟
9ab737ce57 add a gradle demo 2020-11-28 22:51:09 +08:00
金戟
35dbc1f497 also look current thread for test case name 2020-11-28 22:42:25 +08:00
金戟
92f9c27561 dump to snapshot version 2020-11-28 20:36:20 +08:00
金戟
7b273affe3 testable-agent dependence is required in order to have agent jar downloaded 2020-11-28 19:52:20 +08:00
金戟
b4d6a8357e remove dependence on internal class 2020-11-28 19:50:00 +08:00
金戟
ff29172a93 moe descriptions and more faq 2020-11-28 17:03:39 +08:00
金戟
d94daf6910 highlight java code 2020-11-26 07:14:08 +08:00
金戟
fb62ab63cc add usage of verify void method 2020-11-26 07:04:41 +08:00
金戟
d75fdcd4e9 update docs and release 0.3.1 2020-11-24 23:18:12 +08:00
金戟
df9da81f75 use docsify generate doc page 2020-11-22 22:12:51 +08:00
金戟
a8ed46e60d add introduction doc 2020-11-22 10:25:44 +08:00
金戟
47115b22af remove spring framework from demos 2020-11-22 00:46:50 +08:00
金戟
b8b4844c38 add testcase for inherition 2020-11-21 22:53:32 +08:00
金戟
2ae34e0b41 update release note 2020-11-18 11:03:53 +08:00
金戟
401b5f83d7 support enable diagnose log by test class 2020-11-18 10:31:33 +08:00
金戟
5770a702cc add test for times method 2020-11-18 10:29:44 +08:00
金戟
15c7e9c48d add matcher doc, add kotlin matcher test case 2020-11-17 19:47:21 +08:00
金戟
0657c19da3 set to next snapshot version 2020-11-17 19:45:31 +08:00
金戟
b275f32e44 bump version to 0.3.0, fix javadoc 2020-11-17 15:33:39 +08:00
金戟
85276e1c55 maven plugin is usually required 2020-11-17 14:08:14 +08:00
金戟
c68289920f move verify method to verifier class 2020-11-17 14:06:59 +08:00
金戟
7e77f77fa2 print count of mock method found 2020-11-17 13:09:34 +08:00
金戟
40a1a999a7 use variable length args 2020-11-17 13:07:53 +08:00
金戟
7d2dd83410 consider return type when calculating stack change 2020-11-16 23:13:45 +08:00
金戟
43cd4aeeec add test case for matchers 2020-11-16 21:53:32 +08:00
金戟
525e252f96 skip type comparation for macher 2020-11-16 19:19:02 +08:00
金戟
f63b694b0c move matchers to standalone package 2020-11-16 19:11:31 +08:00
金戟
809b1aa57f let's handle invoke and array correctly 2020-11-16 19:01:29 +08:00
金戟
f7dba2d33d add instrument process debug log 2020-11-16 14:47:49 +08:00
金戟
36e260688e need to increase stack by 1 2020-11-16 14:25:20 +08:00
金戟
648e4aa9cc print code location in warning message 2020-11-16 14:16:58 +08:00
Fan Lin
80499f0da2 update release note 2020-11-15 14:37:44 +08:00
Fan Lin
d7ffbcbc32 remove unused mock type 2020-11-15 14:36:57 +08:00
Fan Lin
e586d5db79 handle matcher in verification 2020-11-15 14:34:29 +08:00
金戟
bf9ceeea0f use release profile 2020-11-14 07:06:36 +08:00
金戟
73ef418f39 complete pom file information 2020-11-14 06:54:56 +08:00
金戟
c6ea4c168d add verify matcher 2020-11-13 23:41:34 +08:00
金戟
3f59e9e098 opensourced with MIT license 2020-11-10 14:12:25 +08:00
金戟
754d9ce08d update upcoming version feature 2020-11-10 14:10:14 +08:00
金戟
72269fbcd8 rename to testable mock 2020-11-10 13:06:48 +08:00
金戟
6e47057d7f add test case for final field modification 2020-11-10 11:20:21 +08:00
金戟
854c062e93 implement getter of private field 2020-11-10 11:01:22 +08:00
金戟
baf3c8d657 add verify method to doc 2020-11-10 06:54:01 +08:00
金戟
7e88813518 v0.2.2 released 2020-11-07 23:39:21 +08:00
金戟
88fddf2bcc allow to check method never invoke with parameters 2020-11-07 23:24:53 +08:00
金戟
ba2a823c19 add second solution for IDE test problem 2020-11-05 16:53:01 +08:00
金戟
4b282179f4 fix tools.jar issue with java 9+ 2020-11-04 11:48:07 +08:00
金戟
6454ae547e quick check several invocation with same parameters 2020-11-01 13:43:56 +08:00
金戟
5b7382e9b0 require more stack space 2020-11-01 13:42:44 +08:00
金戟
dc904496fc rename verify method names 2020-11-01 09:41:01 +08:00
金戟
6f74ba771c upgrade surefire plugin to fix conflict with junit 5 2020-11-01 09:02:00 +08:00
金戟
df63bc5c74 fix unsafe access warning 2020-11-01 07:53:04 +08:00
金戟
da3eca3d49 mn.parameters could be null 2020-10-31 22:29:22 +08:00
金戟
d816726f4f implementation parameter verification 2020-10-31 21:47:03 +08:00
金戟
4b1b1c3126 bump version to 0.2.2 snapshot 2020-10-31 18:39:38 +08:00
金戟
649fdebda7 record parameters of mock invoke 2020-10-31 18:27:28 +08:00
金戟
24e4b4f317 refactor invoke record logic to its own util 2020-10-30 21:39:02 +08:00
金戟
0b9a9c7ac6 version tips 2020-10-30 17:59:10 +08:00
金戟
e33c208b8d split private access and mock demo class 2020-10-28 09:12:25 +08:00
249 changed files with 11858 additions and 1911 deletions

16
.gitignore vendored
View File

@@ -1,10 +1,11 @@
# maven ignore
target/
*.jar
*.war
*.zip
*.tar
*.tar.gz
.mvn/
# gradle ignore
build/
out/
.gradle/
# eclipse ignore
.settings/
@@ -34,3 +35,8 @@ Thumbs.db
# others
note/
*.jar
*.war
*.zip
*.tar
*.tar.gz

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Alibaba
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,17 +1,38 @@
# Testable
# TestableMock
基于代码和字节码增强的Java单元测试辅助工具包含以下功能
换种思路写Mock让单元测试更简单。
- 使单元测试能直接调用和访问被测类的私有成员,解决私有方法无法测试的问题
- 使被测类的任意方法调用快速替换为Mock实现"指哪换哪"解决传统Mock工具使用繁琐的问题
无需初始化不挑服务框架甭管要换的是私有方法、静态方法、构造方法还是其他任何类的任何方法也甭管要换的对象是怎么创建的。写好Mock定义加个`@MockMethod`注解,一切统统搞定。
- 文档https://alibaba.github.io/testable-mock/
- 国内文档镜像http://freyrlin.gitee.io/testable-mock/
阅读[这里](https://mp.weixin.qq.com/s/KyU6Eu7mDkZU8FspfSqfMw)了解更多故事。
<font size="5">**0.6版本已发布**</font>,从`0.5.x`升级到`0.6.x`版本请参考[版本升级指南](https://alibaba.github.io/testable-mock/#/zh-cn/doc/upgrade-guide)
如果有遇到其他任何使用问题和建议,请直接在[Issues](https://github.com/alibaba/testable-mock/issues)中提出,也可通过[Pull Request](https://github.com/alibaba/testable-mock/pulls)提交您的代码我们将在24小时内回复并处理
-----
## 版本计划
`TestableMock`正在持续迭代演进,以下版本计划可能在开发过程中发生调整
- `0.6` 当前版本,进行中的工作内容参考[Issue](https://github.com/alibaba/testable-mock/issues)清单
- `0.7` 完善Mock功能的防代码重构机制支持快速Mock指定类型的所有方法
- `1.0` 功能稳定,一个崭新的开始
## 目录结构
```bash
|-- testable-core 核心组件,提供注解和工具类
|-- testable-processor ➜ 编译期代码预处理组件,提供测试辅助功能
|-- testable-agent ➜ JavaAgent组件提供Mock测试相关功能
|-- testable-maven-plugin ➜ Maven插件组件用于简化JavaAgent注入
|-- testable-parent提供各子模块的公共父pom文件
|-- testable-all ➜ 依赖聚合,便于一次性引用所有子模块功能
|-- testable-processor ➜ 编译期代码预处理模块,提供测试辅助功能
|-- testable-agent ➜ JavaAgent模块提供Mock测试相关功能
|-- testable-core ➜ 基础功能模块提供Mock相关注解和工具类
|-- testable-maven-plugin ➜ Maven插件模块用于简化JavaAgent注入
|-- tool ➜ 项目开发过程中的工具脚本
|-- demo
| |-- java-demo ➜ Java语言的示例代码
| `-- kotlin-demo ➜ Kotlin语言的示例代码
@@ -23,5 +44,14 @@
主项目使用JDK 1.6+和Maven 3+版本构建,其中`demo`子项目需要JDK 1.8+版本。
```bash
mvn clean package
mvn clean install
```
## 本地生成文档
```bash
docsify serve docs
```
> TestableMock文档使用`docsify`工具生成,构建前请安装[nodejs](https://nodejs.org/en/download/)运行时,并使用`npm install -g docsify`命令安装文档生成工具。

48
README_EN.md Normal file
View File

@@ -0,0 +1,48 @@
# TestableMock
Write mock faster, make unit testing easier.
Any test framework, no initialization, no matter private method, static method, construction method, or any other method of any class, and no matter how the object created.
Write a mock method, add an `@MockMethod` annotation, everything is done.
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
|-- testable-parent ➜ Provide parent pom file shared by other modules
|-- 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-maven-plugin ➜ Maven plugin module, for simplify JavaAgent injection
|-- tool ➜ Scripts for project maintain
|-- demo
| |-- java-demo ➜ Java code example
| `-- kotlin-demo ➜ Kotlin code example
`-- docs ➜ Source code of usage document
```
## Build project
The project is built using JDK 1.6+ and Maven 3+, except for the `demo` sub-project requires JDK 1.8+.
```bash
mvn clean install
```
## Generate document
```bash
docsify serve docs
```
> `TestableMock` document is generated by `docsify` tool, please install [nodejs](https://nodejs.org/en/download/) before execution, and run `npm install -g docsify` command to install the tool required.

View File

@@ -0,0 +1,27 @@
plugins {
id 'java'
}
group = 'com.alibaba.testable'
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.6.2')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.6.2')
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
test {
jvmArgs "-javaagent:${classpath.find { it.name.contains("testable-agent") }.absolutePath}"
useJUnitPlatform()
}

View File

@@ -1,105 +1,83 @@
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.alibaba.testable</groupId>
<artifactId>java-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>java-demo</name>
<description>Demo project for Spring Boot</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>java-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>java-demo</name>
<description>Demo project for TestableMock</description>
<properties>
<java.version>1.8</java.version>
<testable.version>0.2.1-SNAPSHOT</testable.version>
</properties>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>5.6.2</junit.version>
<testable.version>0.6.2</testable.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</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>
<dependency>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-processor</artifactId>
<version>${testable.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
<exclusion>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- <plugin>-->
<!-- <groupId>org.apache.maven.plugins</groupId>-->
<!-- <artifactId>maven-surefire-plugin</artifactId>-->
<!-- <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>
</plugins>
</build>
<build>
<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>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,62 @@
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;
/**
* 演示父类变量引用子类对象时的Mock场景
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
*/
public class DemoInherit {
/**
* call method overridden by sub class via parent class variable
*/
public Box putIntoBox() {
Box box = new BlackBox("");
box.put("data");
return box;
}
/**
* call method overridden by sub class via sub class variable
*/
public BlackBox putIntoBlackBox() {
BlackBox box = new BlackBox("");
box.put("data");
return box;
}
/**
* call method defined in parent class via parent class variable
*/
public String getFromBox() {
Box box = new BlackBox("data");
return box.get();
}
/**
* call method defined in parent class via sub class variable
*/
public String getFromBlackBox() {
BlackBox box = new BlackBox("data");
return box.get();
}
/**
* call method defined in interface via interface variable
*/
public String getColorViaColor() {
Color color = new BlackBox("");
return color.getColor();
}
/**
* call method defined in interface via sub class variable
*/
public String getColorViaBox() {
BlackBox box = new BlackBox("");
return box.getColor();
}
}

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

@@ -0,0 +1,70 @@
package com.alibaba.demo.basic;
import com.alibaba.demo.basic.model.mock.BlackBox;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
/**
* 演示Mock方法调用校验器
* Demonstrate mock method invocation verifier
*/
public class DemoMatcher {
/**
* Method to be mocked
*/
private void methodToBeMocked() {
// pretend to have some code here
}
/**
* Method to be mocked
*/
private void methodToBeMocked(Object a1, Object a2) {
// pretend to have some code here
}
/**
* Method to be mocked
*/
private void methodToBeMocked(Object[] a) {
// pretend to have some code here
}
public void callMethodWithoutArgument() {
methodToBeMocked();
}
public void callMethodWithNumberArguments() {
// named variable and lambda variable will be recorded as different type
// should have them both in test case
List<Float> floatList = new ArrayList<>();
floatList.add(1.0F);
floatList.add(2.0F);
Long[] longArray = new Long[]{1L, 2L};
methodToBeMocked(1, 2);
methodToBeMocked(1L, 2.0);
methodToBeMocked(new ArrayList<Integer>(){{ add(1); }}, new HashSet<Float>(){{ add(1.0F); }});
methodToBeMocked(1.0, new HashMap<Integer, Float>(2){{ put(1, 1.0F); }});
methodToBeMocked(floatList, floatList);
methodToBeMocked(longArray);
methodToBeMocked(new Double[]{1.0, 2.0});
}
public void callMethodWithStringArgument() {
methodToBeMocked("hello", "world");
methodToBeMocked("testable", "mock");
methodToBeMocked(new String[]{"demo"});
}
public void callMethodWithObjectArgument() {
methodToBeMocked(new BlackBox("hello"), new BlackBox("world"));
methodToBeMocked(new BlackBox("demo"), null);
methodToBeMocked(null, new BlackBox("demo"));
}
}

View File

@@ -0,0 +1,67 @@
package com.alibaba.demo.basic;
import com.alibaba.demo.basic.model.mock.BlackBox;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Collectors;
/**
* 演示基本的Mock功能
* Demonstrate basic mock functionality
*/
public class DemoMock {
/**
* method with new operation
*/
public String newFunc() {
BlackBox component = new BlackBox("something");
return component.get();
}
/**
* method with member method invoke
*/
public String outerFunc(String s) throws Exception {
return "{ \"res\": \"" + innerFunc(s) + staticFunc() + "\"}";
}
/**
* method with common method invoke
*/
public String commonFunc() {
return "anything".trim() + "__" + "anything".substring(1, 2) + "__" + "abc".startsWith("ab");
}
/**
* method with static method invoke
*/
public BlackBox getBox() {
return BlackBox.secretBox();
}
/**
* two methods invoke same private method
*/
public String callerOne() {
return callFromDifferentMethod();
}
public String callerTwo() {
return callFromDifferentMethod();
}
private static String staticFunc() {
return "_STATIC_TAIL";
}
private String innerFunc(String s) throws Exception {
return Files.readAllLines(Paths.get("/a-not-exist-file")).stream().collect(Collectors.joining());
}
private String callFromDifferentMethod() {
return "realOne";
}
}

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

@@ -0,0 +1,39 @@
package com.alibaba.demo.basic;
import java.util.*;
/**
* 演示模板方法的Mock场景
* Demonstrate scenario of mocking template method
*/
public class DemoTemplate {
private <T> List<T> getList(T value) {
List<T> l = new ArrayList<>();
l.add(value);
return l;
}
private <K, V> Map<K, V> getMap(K key, V value) {
Map<K, V> m = new HashMap<>();
m.put(key, value);
return m;
}
public String singleTemplateMethod() {
List<String> list = getList("demo");
return list.get(0);
}
public String doubleTemplateMethod() {
Map<String, String> map = getMap("hello", "testable");
return map.get("hello");
}
public Set<?> newTemplateMethod() {
Set<String> set = new HashSet<>();
set.add("world");
return set;
}
}

View File

@@ -1,24 +1,23 @@
package com.alibaba.testable.demo;
package com.alibaba.demo.basic.model.mock;
public class BlackBox implements Box {
private String data;
@Override
public void put(String something) {
data = something;
}
public class BlackBox extends Box implements Color {
public BlackBox(String data) {
this.data = data;
}
public String get() {
return data;
}
public static BlackBox secretBox() {
return new BlackBox("secret");
}
@Override
public void put(String something) {
data = something;
}
@Override
public String getColor() {
return "black";
}
}

View File

@@ -0,0 +1,13 @@
package com.alibaba.demo.basic.model.mock;
abstract public class Box {
protected String data;
abstract public void put(String something);
public String get() {
return data;
}
}

View File

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

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,7 +0,0 @@
package com.alibaba.testable.demo;
public interface Box {
void put(String something);
}

View File

@@ -1,12 +0,0 @@
package com.alibaba.testable.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

View File

@@ -1,85 +0,0 @@
package com.alibaba.testable.demo;
import org.springframework.stereotype.Service;
import sun.net.www.http.HttpClient;
import java.net.URL;
@Service
public class DemoService {
private int count;
/**
* Target 1 - private method
*/
private String privateFunc(String s, int i) {
return s + " - " + i;
}
/**
* Target 2 - method with private field access
*/
public String privateFieldAccessFunc() {
count += 2;
return String.valueOf(count);
}
/**
* Target 3 - method with new operation
*/
public String newFunc() {
BlackBox component = new BlackBox("something");
return component.get();
}
/**
* Target 4 - method with member method invoke
*/
public String outerFunc(String s) throws Exception {
return "{ \"res\": \"" + innerFunc(s) + "\"}";
}
/**
* Target 5 - method with common method invoke
*/
public String commonFunc() {
return "anything".trim() + "__" + "anything".substring(1, 2) + "__" + "abc".startsWith("ab");
}
/**
* Target 6 - method with static method invoke
*/
public BlackBox getBox() {
return BlackBox.secretBox();
}
/**
* Target 7 - method with override method invoke
*/
public Box putBox() {
Box box = new BlackBox("");
box.put("data");
return box;
}
/**
* Target 8 - two methods invoke same private method
*/
public String callerOne() {
return callFromDifferentMethod();
}
public String callerTwo() {
return callFromDifferentMethod();
}
private String innerFunc(String s) throws Exception {
return HttpClient.New(new URL("http:/xxx/" + s)).getURLFile();
}
private String callFromDifferentMethod() {
return "realOne";
}
}

View File

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

View File

@@ -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

@@ -0,0 +1,85 @@
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 org.junit.jupiter.api.Test;
import static com.alibaba.testable.core.matcher.InvokeMatcher.*;
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
import static org.junit.jupiter.api.Assertions.fail;
/**
* 演示Mock方法调用校验器
* Demonstrate mock method invocation verifier
*/
class DemoMatcherTest {
private DemoMatcher demoMatcher = new DemoMatcher();
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) {}
}
@Test
void should_match_no_argument() {
demoMatcher.callMethodWithoutArgument();
verify("methodWithoutArgument").withTimes(1);
demoMatcher.callMethodWithoutArgument();
verify("methodWithoutArgument").withTimes(2);
}
@Test
void should_match_number_arguments() {
demoMatcher.callMethodWithNumberArguments();
verify("methodWithArguments").without(anyString(), 2);
verify("methodWithArguments").withInOrder(anyInt(), 2);
verify("methodWithArguments").withInOrder(anyLong(), anyNumber());
verify("methodWithArguments").with(1.0, anyMapOf(Integer.class, Float.class));
verify("methodWithArguments").with(anyList(), anySetOf(Float.class));
verify("methodWithArguments").with(anyList(), anyListOf(Float.class));
verify("methodWithArrayArgument").with(anyArrayOf(Long.class));
verify("methodWithArrayArgument").with(anyArray());
}
@Test
void should_match_string_arguments() {
demoMatcher.callMethodWithStringArgument();
verify("methodWithArguments").with(startsWith("he"), endsWith("ld"));
verify("methodWithArguments").with(contains("stab"), matches("m.[cd]k"));
verify("methodWithArrayArgument").with(anyArrayOf(String.class));
}
@Test
void should_match_object_arguments() {
demoMatcher.callMethodWithObjectArgument();
verify("methodWithArguments").withInOrder(any(BlackBox.class), any(BlackBox.class));
verify("methodWithArguments").withInOrder(nullable(BlackBox.class), nullable(BlackBox.class));
verify("methodWithArguments").withInOrder(isNull(), notNull());
}
@Test
void should_match_with_times() {
demoMatcher.callMethodWithNumberArguments();
verify("methodWithArguments").with(anyNumber(), any()).times(3);
demoMatcher.callMethodWithNumberArguments();
boolean gotError = false;
try {
verify("methodWithArguments").with(anyNumber(), any()).times(4);
} catch (VerifyFailedError e) {
gotError = true;
}
if (!gotError) {
fail();
}
}
}

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,13 +0,0 @@
package com.alibaba.testable.demo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class DemoApplicationTests {
@Test
void contextLoads() {
}
}

View File

@@ -1,131 +0,0 @@
package com.alibaba.testable.demo;
import com.alibaba.testable.core.accessor.PrivateAccessor;
import com.alibaba.testable.processor.annotation.EnablePrivateAccess;
import com.alibaba.testable.core.annotation.TestableMock;
import org.junit.jupiter.api.Test;
import java.util.concurrent.Executors;
import static com.alibaba.testable.core.tool.TestableTool.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
@EnablePrivateAccess
class DemoServiceTest {
@TestableMock(targetMethod = CONSTRUCTOR)
private BlackBox createBlackBox(String text) {
return new BlackBox("mock_" + text);
}
@TestableMock
private String innerFunc(DemoService self, String text) {
return "mock_" + text;
}
@TestableMock
private String trim(String self) {
return "trim_string";
}
@TestableMock(targetMethod = "substring")
private String sub(String self, int i, int j) {
return "sub_string";
}
@TestableMock
private boolean startsWith(String self, String s) {
return false;
}
@TestableMock
private BlackBox secretBox(BlackBox _) {
return new BlackBox("not_secret_box");
}
@TestableMock
private void put(Box self, String something) {
self.put("put_" + something + "_mocked");
}
@TestableMock
private String callFromDifferentMethod(DemoService 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";
}
}
private DemoService demoService = new DemoService();
@Test
void should_able_to_mock_private_method() throws Exception {
assertEquals("hello - 1", demoService.privateFunc("hello", 1));
assertEquals("hello - 1", PrivateAccessor.invoke(demoService, "privateFunc", "hello", 1));
}
@Test
void should_able_to_mock_private_field() throws Exception {
demoService.count = 2;
assertEquals("4", demoService.privateFieldAccessFunc());
PrivateAccessor.set(demoService, "count", 3);
assertEquals("5", demoService.privateFieldAccessFunc());
assertEquals(new Integer(5), PrivateAccessor.get(demoService, "count"));
}
@Test
void should_able_to_mock_new_object() throws Exception {
assertEquals("mock_something", demoService.newFunc());
verify("createBlackBox").times(1);
}
@Test
void should_able_to_mock_member_method() throws Exception {
assertEquals("{ \"res\": \"mock_hello\"}", demoService.outerFunc("hello"));
verify("innerFunc").times(1);
}
@Test
void should_able_to_mock_common_method() throws Exception {
assertEquals("trim_string__sub_string__false", demoService.commonFunc());
verify("trim").times(1);
verify("sub").times(1);
verify("startsWith").times(1);
}
@Test
void should_able_to_mock_static_method() throws Exception {
assertEquals("not_secret_box", demoService.getBox().get());
verify("secretBox").times(1);
}
@Test
void should_able_to_mock_override_method() throws Exception {
BlackBox box = (BlackBox)demoService.putBox();
verify("put").times(1);
assertEquals("put_data_mocked", box.get());
}
@Test
void should_able_to_get_source_method_name() throws Exception {
// synchronous
assertEquals("mock_one_mock_others", demoService.callerOne() + "_" + demoService.callerTwo());
// asynchronous
assertEquals("mock_one_mock_others",
Executors.newSingleThreadExecutor().submit(() -> demoService.callerOne() + "_" + demoService.callerTwo()).get());
verify("callFromDifferentMethod").times(4);
}
@Test
void should_able_to_get_test_case_name() throws Exception {
// synchronous
assertEquals("mock_special", demoService.callerOne());
// asynchronous
assertEquals("mock_special", Executors.newSingleThreadExecutor().submit(() -> demoService.callerOne()).get());
verify("callFromDifferentMethod").times(2);
}
}

View File

@@ -0,0 +1 @@
omni.constructor.enhance.enable = true

View File

@@ -0,0 +1,33 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.4.10"
}
group = "com.alibaba.testable"
version = "1.0.0-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_1_8
repositories {
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.6.2")
testAnnotationProcessor("com.alibaba.testable:testable-processor:0.6.2")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "1.8"
}
}
tasks.withType<Test> {
jvmArgs("-javaagent:${classpath.find { it.name.contains("testable-agent") }!!.absolutePath}")
useJUnitPlatform()
}

View File

@@ -1,135 +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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.alibaba.testable</groupId>
<artifactId>kotlin-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>kotlin-demo</name>
<description>Demo project for Spring Boot</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>
<testable.version>0.2.1-SNAPSHOT</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.2</testable.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</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-processor</artifactId>
<version>${testable.version}</version>
<scope>provided</scope>
</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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- <plugin>-->
<!-- <groupId>org.apache.maven.plugins</groupId>-->
<!-- <artifactId>maven-surefire-plugin</artifactId>-->
<!-- <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.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xjsr305=strict</arg>
</args>
<compilerPlugins>
<plugin>spring</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</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>
</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

@@ -0,0 +1,66 @@
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
/**
* 演示父类变量引用子类对象时的Mock场景
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
*/
class DemoInherit {
/**
* call method overridden by sub class via parent class variable
*/
fun putIntoBox(): Box {
val box: Box = BlackBox("")
box.put("data")
return box
}
/**
* call method overridden by sub class via sub class variable
*/
fun putIntoBlackBox(): BlackBox {
val box = BlackBox("")
box.put("data")
return box
}
/**
* call method defined in parent class via parent class variable
*/
val fromBox: String?
get() {
val box: Box = BlackBox("data")
return box.get()
}
/**
* call method defined in parent class via sub class variable
*/
val fromBlackBox: String?
get() {
val box = BlackBox("data")
return box.get()
}
/**
* call method defined in interface via interface variable
*/
val colorViaColor: String
get() {
val color: Color = BlackBox("")
return color.color
}
/**
* call method defined in interface via sub class variable
*/
val colorViaBox: String
get() {
val box = BlackBox("")
return box.color
}
}

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

@@ -0,0 +1,79 @@
package com.alibaba.demo.basic
import com.alibaba.demo.basic.model.mock.BlackBox
import java.util.*
/**
* 演示Mock方法调用校验器
* Demonstrate mock method invocation verifier
*/
class DemoMatcher {
/**
* Method to be mocked
*/
private fun methodToBeMocked() {
// pretend to have some code here
}
/**
* Method to be mocked
*/
private fun methodToBeMocked(a1: Any?, a2: Any?) {
// pretend to have some code here
}
/**
* Method to be mocked
*/
private fun methodToBeMocked(a: Array<out Any>) {
// pretend to have some code here
}
fun callMethodWithoutArgument() {
methodToBeMocked()
}
fun callMethodWithNumberArguments() {
// named variable and lambda variable will be recorded as different type
// should have them both in test case
val floatList: MutableList<Float> = ArrayList()
floatList.add(1.0f)
floatList.add(2.0f)
val longArray = arrayOf(1L, 2L)
methodToBeMocked(1, 2)
methodToBeMocked(1L, 2.0)
// below two invocations are equivalent
methodToBeMocked(listOf(1), setOf(1.0f))
// multiple lines method invocation
methodToBeMocked(object : ArrayList<Int?>() {
init {
add(1)
}
}, object : HashSet<Float?>() {
init {
add(1.0f)
}
})
// below two invocations are equivalent
methodToBeMocked(1.0, mapOf(1 to 1.0f))
methodToBeMocked(1.0, object : HashMap<Int?, Float?>(2) { init { put(1, 1.0f) } })
methodToBeMocked(floatList, floatList)
methodToBeMocked(longArray)
methodToBeMocked(arrayOf(1.0, 2.0))
}
fun callMethodWithStringArgument() {
methodToBeMocked("hello", "world")
methodToBeMocked("testable", "mock")
methodToBeMocked(arrayOf("demo"))
}
fun callMethodWithObjectArgument() {
methodToBeMocked(BlackBox("hello"), BlackBox("world"))
methodToBeMocked(BlackBox("demo"), null)
methodToBeMocked(null, BlackBox("demo"))
}
}

View File

@@ -0,0 +1,69 @@
package com.alibaba.demo.basic
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 {
/**
* method with new operation
*/
fun newFunc(): String? {
return BlackBox("something").get()
}
/**
* method with member method invoke
*/
fun outerFunc(s: String): String {
return "{ \"res\": \"" + innerFunc(s) + staticFunc() + "\"}"
}
/**
* method with common method invoke
*/
fun commonFunc(): String {
val box = BlackBox("anything")
return box.trim() + "__" + box.substring(1, 2) + "__" + box.startsWith("any")
}
/**
* method with static method invoke
*/
fun getBox(): BlackBox {
return ColorBox.createBox("Red", BlackBox.secretBox())
}
/**
* two methods invoke same private method
*/
fun callerOne(): String {
return callFromDifferentMethod()
}
fun callerTwo(): String {
return callFromDifferentMethod()
}
private fun innerFunc(s: String): String {
return Files.readAllLines(Paths.get("/a-not-exist-file")).joinToString()
}
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

@@ -0,0 +1,39 @@
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()
l.add(value)
return l
}
private fun <K, V> getMap(key: K, value: V): Map<K, V> {
val m: MutableMap<K, V> = HashMap()
m[key] = value
return m
}
fun singleTemplateMethod(): String {
val list = getList("demo")
return list[0]
}
fun doubleTemplateMethod(): String? {
val map = getMap("hello", "testable")
return map["hello"]
}
fun newTemplateMethod(): Set<*> {
val set: MutableSet<String> = HashSet()
set.add("world")
return set
}
}

View File

@@ -0,0 +1,41 @@
package com.alibaba.demo.basic.model.mock
class BlackBox(var input: String) : Box(), Color {
init {
this.content = input
}
override fun put(something: String) {
content = something
}
override val color: String
get() = "black"
fun trim(): String? {
return content?.trim()
}
fun substring(from: Int, to: Int): String? {
return content?.substring(from, to)
}
fun startsWith(prefix: String): Boolean {
return content?.startsWith(prefix) == true
}
companion object {
fun secretBox(): BlackBox {
return BlackBox("secret")
}
}
}
object ColorBox {
fun createBox(color: String, box: BlackBox): BlackBox {
return BlackBox("${color}_${box.get()}")
}
}

View File

@@ -0,0 +1,13 @@
package com.alibaba.demo.basic.model.mock
abstract class Box {
var content: String? = null
abstract fun put(something: String)
open fun get(): String? {
return content
}
}

View File

@@ -0,0 +1,7 @@
package com.alibaba.demo.basic.model.mock
interface Color {
val color: String
}

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,9 +1,9 @@
package com.alibaba.testable.demo
package com.alibaba.demo.java2kotlin
import java.io.File
import java.io.IOException
object PathUtil {
object PathDemo {
fun deleteRecursively(file: File) {
if (!file.exists()) {

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,38 +0,0 @@
package com.alibaba.testable.demo
class BlackBox(private var data: String) : Box {
override fun put(something: String) {
data = something
}
fun get(): String {
return data
}
fun trim(): String {
return data.trim()
}
fun substring(from: Int, to: Int): String {
return data.substring(from, to)
}
fun startsWith(prefix: String): Boolean {
return data.startsWith(prefix)
}
companion object {
fun secretBox(): BlackBox {
return BlackBox("secret")
}
}
}
object ColorBox {
fun createBox(color: String, box: BlackBox): BlackBox {
return BlackBox("${color}_${box.get()}")
}
}

View File

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

View File

@@ -1,11 +0,0 @@
package com.alibaba.testable.demo
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}

View File

@@ -1,80 +0,0 @@
package com.alibaba.testable.demo
import org.springframework.stereotype.Service
import sun.net.www.http.HttpClient
import java.net.URL
@Service
class DemoService {
private var count = 0
/**
* Target 1 - private method
*/
private fun privateFunc(s: String, i: Int): String {
return "$s - $i"
}
/**
* Target 2 - method with private field access
*/
fun privateFieldAccessFunc(): String {
count += 2
return count.toString()
}
/**
* Target 3 - method with new operation
*/
fun newFunc(): String {
return BlackBox("something").get()
}
/**
* Target 4 - method with member method invoke
*/
fun outerFunc(s: String): String {
return "{ \"res\": \"" + innerFunc(s) + "\"}"
}
/**
* Target 5 - method with common method invoke
*/
fun commonFunc(): String {
val box = BlackBox("anything")
return box.trim() + "__" + box.substring(1, 2) + "__" + box.startsWith("any")
}
/**
* Target 6 - method with static method invoke
*/
fun getBox(): BlackBox {
return ColorBox.createBox("Red", BlackBox.secretBox())
}
/**
* Target 7 - method with override method invoke
*/
fun putBox(): Box {
val box: Box = BlackBox("")
box.put("data")
return box
}
/**
* Target 8 - two methods invoke same private method
*/
fun callerOne(): String {
return callFromDifferentMethod()
}
fun callerTwo(): String {
return callFromDifferentMethod()
}
private fun innerFunc(s: String) = HttpClient.New(URL("http:/xxx/$s")).urlFile
private fun callFromDifferentMethod() = "realOne"
}

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

@@ -0,0 +1,87 @@
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.demo.basic.model.mock.BlackBox
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
/**
* 演示Mock方法调用校验器
* Demonstrate mock method invocation verifier
*/
internal class DemoMatcherTest {
private val demoMatcher = com.alibaba.demo.basic.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>) {
}
}
@Test
fun should_match_no_argument() {
demoMatcher.callMethodWithoutArgument()
InvokeVerifier.verify("methodWithoutArgument").withTimes(1)
demoMatcher.callMethodWithoutArgument()
InvokeVerifier.verify("methodWithoutArgument").withTimes(2)
}
@Test
fun should_match_number_arguments() {
demoMatcher.callMethodWithNumberArguments()
InvokeVerifier.verify("methodWithArguments").without(InvokeMatcher.anyString(), 2)
InvokeVerifier.verify("methodWithArguments").withInOrder(InvokeMatcher.anyInt(), 2)
InvokeVerifier.verify("methodWithArguments").withInOrder(InvokeMatcher.anyLong(), InvokeMatcher.anyNumber())
// Note: Must use `::class.javaObjectType` for primary types check in Kotlin
InvokeVerifier.verify("methodWithArguments").with(1.0, InvokeMatcher.anyMapOf(Int::class.javaObjectType, Float::class.javaObjectType)).times(2)
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyList(), InvokeMatcher.anySetOf(Float::class.javaObjectType)).times(2)
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyList(), InvokeMatcher.anyListOf(Float::class.javaObjectType))
InvokeVerifier.verify("methodWithArrayArgument").with(InvokeMatcher.anyArrayOf(Long::class.javaObjectType))
InvokeVerifier.verify("methodWithArrayArgument").with(InvokeMatcher.anyArray())
}
@Test
fun should_match_string_arguments() {
demoMatcher.callMethodWithStringArgument()
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.startsWith("he"), InvokeMatcher.endsWith("ld"))
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.contains("stab"), InvokeMatcher.matches("m.[cd]k"))
InvokeVerifier.verify("methodWithArrayArgument").with(InvokeMatcher.anyArrayOf(String::class.java))
}
@Test
fun should_match_object_arguments() {
demoMatcher.callMethodWithObjectArgument()
InvokeVerifier.verify("methodWithArguments").withInOrder(InvokeMatcher.any(BlackBox::class.java), InvokeMatcher.any(BlackBox::class.java))
InvokeVerifier.verify("methodWithArguments").withInOrder(InvokeMatcher.nullable(BlackBox::class.java), InvokeMatcher.nullable(BlackBox::class.java))
InvokeVerifier.verify("methodWithArguments").withInOrder(InvokeMatcher.isNull(), InvokeMatcher.notNull())
}
@Test
fun should_match_with_times() {
demoMatcher.callMethodWithNumberArguments()
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyNumber(), InvokeMatcher.any()).times(4)
demoMatcher.callMethodWithNumberArguments()
var gotError = false
try {
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyNumber(), InvokeMatcher.any()).times(5)
} catch (e: VerifyFailedError) {
gotError = true
}
if (!gotError) {
Assertions.fail<Any>()
}
}
}

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

@@ -0,0 +1,46 @@
package com.alibaba.demo.basic
import com.alibaba.testable.core.tool.PrivateAccessor
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
/**
* 演示私有成员访问功能
* Demonstrate private member access functionality
*/
internal class DemoPrivateAccessorTest {
private val demoPrivateAccess = DemoPrivateAccess()
@Test
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_access_private_field() {
PrivateAccessor.set(demoPrivateAccess, "count", 3)
assertEquals(3, PrivateAccessor.get(demoPrivateAccess, "count"))
}
@Test
fun should_access_private_static_method() {
val list = listOf("a", "b", "c");
assertEquals("hello + 1", PrivateAccessor.invokeStatic(DemoPrivateAccess::class.java, "privateStaticFunc", "hello", 1))
assertEquals("abc * hello * 1", PrivateAccessor.invokeStatic(DemoPrivateAccess::class.java, "privateJvmStaticFunc", list, "hello", 1))
}
@Test
fun should_access_private_static_field() {
PrivateAccessor.setStatic(DemoPrivateAccess::class.java, "staticCount", 3)
assertEquals(3, PrivateAccessor.getStatic(DemoPrivateAccess::class.java, "staticCount"))
}
@Test
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 PathDemoTest {
class Mock {
@MockMethod
fun exists(f: File): Boolean {
return when (f.absolutePath) {
"/a/b" -> true
"/a/b/c" -> true
else -> f.exists()
}
}
@MockMethod
fun isDirectory(f: File): Boolean {
return when (f.absolutePath) {
"/a/b/c" -> true
else -> f.isDirectory
}
}
@MockMethod
fun delete(f: File): Boolean {
return true
}
@MockMethod
fun listFiles(f: File): Array<File>? {
return when (f.absolutePath) {
"/a/b" -> arrayOf(File("/a/b/c"), File("/a/b/d"))
"/a/b/c" -> arrayOf(File("/a/b/c/e"))
else -> f.listFiles()
}
}
}
@Test
fun should_mock_java_method_invoke_in_kotlin() {
PathDemo.deleteRecursively(File("/a/b/"))
verify("listFiles").withTimes(2)
verify("delete").withTimes(4)
}
}

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,13 +0,0 @@
package com.alibaba.testable.demo
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
@SpringBootTest
class DemoApplicationTests {
@Test
fun contextLoads() {
}
}

View File

@@ -1,126 +0,0 @@
package com.alibaba.testable.demo
import com.alibaba.testable.core.accessor.PrivateAccessor
import com.alibaba.testable.core.annotation.TestableMock
import com.alibaba.testable.core.tool.TestableTool.*
import com.alibaba.testable.processor.annotation.EnablePrivateAccess
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.util.concurrent.Executors
@EnablePrivateAccess
internal class DemoServiceTest {
@TestableMock(targetMethod = CONSTRUCTOR)
private fun createBlackBox(text: String) = BlackBox("mock_$text")
@TestableMock
private fun innerFunc(self: DemoService, text: String) = "mock_$text"
@TestableMock
private fun trim(self: BlackBox) = "trim_string"
@TestableMock(targetMethod = "substring")
private fun sub(self: BlackBox, i: Int, j: Int) = "sub_string"
@TestableMock
private fun startsWith(self: BlackBox, s: String) = false
@TestableMock
private fun secretBox(ignore: BlackBox): BlackBox {
return BlackBox("not_secret_box")
}
@TestableMock
private fun createBox(ignore: ColorBox, color: String, box: BlackBox): BlackBox {
return BlackBox("White_${box.get()}")
}
@TestableMock
private fun put(self: Box, something: String) {
self.put("put_" + something + "_mocked")
}
@TestableMock
private fun callFromDifferentMethod(self: DemoService): String {
return if (TEST_CASE == "should_able_to_get_test_case_name") {
"mock_special"
} else {
when (SOURCE_METHOD) {
"callerOne" -> "mock_one"
else -> "mock_others"
}
}
}
private val demoService = DemoService()
@Test
fun should_able_to_mock_private_method() {
assertEquals("hello - 1", PrivateAccessor.invoke(demoService, "privateFunc", "hello", 1))
}
@Test
fun should_able_to_mock_private_field() {
PrivateAccessor.set(demoService, "count", 3)
assertEquals("5", demoService.privateFieldAccessFunc())
assertEquals(5, PrivateAccessor.get(demoService, "count"))
}
@Test
fun should_able_to_mock_new_object() {
assertEquals("mock_something", demoService.newFunc())
verify("createBlackBox").times(1)
}
@Test
fun should_able_to_mock_member_method() {
assertEquals("{ \"res\": \"mock_hello\"}", demoService.outerFunc("hello"))
verify("innerFunc").times(1)
}
@Test
fun should_able_to_mock_common_method() {
assertEquals("trim_string__sub_string__false", demoService.commonFunc())
verify("trim").times(1)
verify("sub").times(1)
verify("startsWith").times(1)
}
@Test
fun should_able_to_mock_static_method() {
assertEquals("White_not_secret_box", demoService.getBox().get())
verify("secretBox").times(1)
verify("createBox").times(1)
}
@Test
fun should_able_to_mock_override_method() {
val box = demoService.putBox() as BlackBox
verify("put").times(1)
assertEquals("put_data_mocked", box.get())
}
@Test
fun should_able_to_get_source_method_name() {
// synchronous
assertEquals("mock_one_mock_others", demoService.callerOne() + "_" + demoService.callerTwo())
// asynchronous
assertEquals("mock_one_mock_others", Executors.newSingleThreadExecutor().submit<String> {
demoService.callerOne() + "_" + demoService.callerTwo()
}.get())
verify("callFromDifferentMethod").times(4)
}
@Test
fun should_able_to_get_test_case_name() {
// synchronous
assertEquals("mock_special", demoService.callerOne())
// asynchronous
assertEquals("mock_special", Executors.newSingleThreadExecutor().submit<String> {
demoService.callerOne()
}.get())
verify("callFromDifferentMethod").times(2)
}
}

View File

@@ -1,48 +0,0 @@
package com.alibaba.testable.demo
import org.junit.jupiter.api.Test
import com.alibaba.testable.core.annotation.TestableMock
import com.alibaba.testable.core.tool.TestableTool.verify
import java.io.File
class PathUtilTest {
@TestableMock
fun exists(f: File): Boolean {
return when (f.absolutePath) {
"/a/b" -> true
"/a/b/c" -> true
else -> f.exists()
}
}
@TestableMock
fun isDirectory(f: File): Boolean {
return when (f.absolutePath) {
"/a/b/c" -> true
else -> f.isDirectory
}
}
@TestableMock
fun delete(f: File): Boolean {
return true
}
@TestableMock
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").times(2)
verify("delete").times(4)
}
}

View File

@@ -0,0 +1 @@
omni.constructor.enhance.enable = true

View File

@@ -12,6 +12,7 @@
<modules>
<module>java-demo</module>
<module>kotlin-demo</module>
<module>spock-demo</module>
</modules>
</project>

5
demo/settings.gradle Normal file
View File

@@ -0,0 +1,5 @@
rootProject.name = 'demos'
include 'java-demo'
include 'kotlin-demo'
include 'spock-demo'

View File

@@ -0,0 +1,28 @@
plugins {
id 'groovy'
}
group = 'com.alibaba.testable'
version = '1.0.0-SNAPSHOT'
sourceCompatibility = '8'
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
testImplementation 'org.codehaus.groovy:groovy-all:3.0.7'
testImplementation 'org.spockframework:spock-core:2.0-M5-groovy-3.0'
testImplementation('com.alibaba.testable:testable-all:0.6.2')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.6.2')
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
test {
jvmArgs "-javaagent:${classpath.find { it.name.contains("testable-agent") }.absolutePath}"
useJUnitPlatform()
}

86
demo/spock-demo/pom.xml Normal file
View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Refer to: https://github.com/spockframework/spock-example/blob/master/pom.xml -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.testable</groupId>
<artifactId>spock-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>spock-demo</name>
<description>Spock demo for Testable</description>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<testable.version>0.6.2</testable.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-bom</artifactId>
<version>2.0-M5-groovy-3.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>3.0.7</version>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-junit4</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-all</artifactId>
<version>${testable.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!-- The gmavenplus plugin is used to compile Groovy code -->
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.12.0</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compileTests</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<argLine>-javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
<useFile>false</useFile>
<includes>
<include>**/*Test.java</include>
<include>**/*Spec.java</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,19 @@
package com.github.pbetkier.spockdemo;
import com.github.pbetkier.spockdemo.model.SpockBox;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Collectors;
public class DemoSpock {
public SpockBox createBoxOfNum() {
SpockBox box = new SpockBox();
for (int i = 1; i <= 3; i++) {
box.put(String.valueOf(i));
}
return box;
}
}

View File

@@ -0,0 +1,27 @@
package com.github.pbetkier.spockdemo.model;
import java.util.ArrayList;
import java.util.List;
public class SpockBox {
private List<String> contents = new ArrayList<>();
public int size() {
return contents.size();
}
public void put(String data) {
contents.add(data);
}
public String pop() {
if (contents.isEmpty()) {
return null;
}
String data = contents.get(size() - 1);
contents.remove(size() - 1);
return data;
}
}

View File

@@ -0,0 +1,44 @@
package com.github.pbetkier.spockdemo
import com.alibaba.testable.core.annotation.MockConstructor
import com.alibaba.testable.core.annotation.MockMethod
import com.github.pbetkier.spockdemo.model.SpockBox
import spock.lang.Shared
import spock.lang.Specification
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
class DemoSpockTest extends Specification {
@Shared
def demoSpock = new DemoSpock()
static class Mock {
@MockConstructor
SpockBox createBox() {
SpockBox box = new SpockBox()
box.put("mock zero")
return box
}
@MockMethod(targetMethod = "put")
void putBox(SpockBox self, String data) {
self.put("mock " + data)
}
}
def "should get a box of numbers"() {
given:
def box = demoSpock.createBoxOfNum()
expect:
box.size() == 4
box.pop() == "mock 3"
box.pop() == "mock 2"
box.pop() == "mock 1"
box.pop() == "mock zero"
verify("createBox").withTimes(1)
verify("putBox").withInOrder("1").withInOrder("2").withInOrder("3")
}
}

13
docs/en-us/README.md Normal file
View File

@@ -0,0 +1,13 @@
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.
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 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**.
![mock](https://testable-code.oss-cn-beijing.aliyuncs.com/en-us/mock-simpson.png)

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

@@ -0,0 +1,26 @@
Mock Tools Comparison
---
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 |
| ---- | ---- | ---- | ---- | ---- | ---- |
| 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 |
| PowerMock | Custom class loader | Class | **No limitation, any method works** | Complicate | **Good** |
| JMockit | Runtime bytecode modification | Class | Except constructor (i.e. new operator) | Complicate | Just so so |
| TestableMock | Runtime bytecode modification | Method | **No limitation, any method works** | **Very easy** | Just so so |
`Mockito` is Java's the most classical mock tool, good stability and easy to use, with both IntelliJ and Eclipse have plugin. The shortcoming part is that its mock functionality is sometimes not enough and have to be used in conjunction with other mock tools when necessary.
`Spock` is a highly readable unit testing framework with built-in mock support, it has a good overall consistency. Since it is also based on dynamic proxy implementation, its shortcomings are similar to `Mockito`.
`PowerMock` is a very powerful mock tool. Its basic syntax is compatible with `Mockito`, and extends many missing features of `Mockito`, including support for mocking of private, static and construction methods. However, due to the use of custom class loader, the test coverage of Jacoco will drop to zero in the default `on-the-fly` mode.
`JMockit` is a mock tool whose functionality and convenience are between `Mockito` and `PowerMock`, and it makes up for their respective shortcomings. The project tried to launch a rewritten version of JMockit2 in 2017 but failed to complete, and is currently in an inactive maintenance state.
The functionality of `TestabledMock` is basically the same as that of `PowerMock`, and it is extremely easy to use. You can complete most tasks only by mastering the annotations of `@MockMethod`.
The 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 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,29 @@
How Mock Works
---
This document mainly introduces the design and implementation principles of the mock function in `TestableMock`.
Unlike common mock-tools which write mock definitions in each test case, `TestableMock` allows each business class to provide its own set of mock methods, describing what should be mocked during testing, and define the corresponding replacement logic (that is, each business class has its own independent "Test class" and independent "Mock class"). By adopting "convention is better than configuration" principle, `TestableMock` not only reduce redundant codes but also reduce the cost of mock learning.
This design is based on two basic assumptions:
1. In any business class, methods that need to be mocked in one test case are usually required to be mocked in other test cases. For these mocked methods usually requiring external dependencies that are not easy to test.
2. Each unit test only focuses on the logic inside the unit under test, and irrelevant calls outside the unit should be replaced with mocks. That is, the invocations that need to be mocked should all be in the code of the class under test.
Accordingly, the unit test scenarios that meet the above assumptions are simplified through conventions, and the remaining more complex use scenarios are supported through configuration.
The mechanism of `TestableMock` can be summarized in one sentence: <u>Using java agent to dynamically modify the bytecode, before the unit test is about to run, replace all invocations in the business class under test which match the mock method definition with invocations to the mock method itself.</u>.
The final effect is that no matter what service framework or object-container the code uses, no matter whether the object of mock target is injected by a framework, created by new operation, and whether the target method of mock is private or external, defined as global, local, static, inherited or overloaded, all can be mocked in a same and simple way, which make unit testing much easier.
> Notice: Mock's goal is the method invocation in the class under test. The code inside the test case will not be mocked, and the method definition itself has not changed, but the invocation code to those methods will be replaced.
Specifically, when the unit test is started, `TestableMock` will preprocess the classes loaded into memory and establish the association relationship between the "class under test", the "test class", and the "mock container class" respectively (can be one-to-one or many-to-one). On the one hand, this association is to correctly match the mock call and replace it before the test case is executed, and on the other hand, it is used to control the effective scope of the mock method.
For the class under test, replace the matched call with a call to the mock container method.
For the test class, insert the mock context initialization code at the beginning of each test case.
For the mock container class, add the `testableIns()` method to make the class become a singleton class, and insert codes to record the call at the beginning of each Mock method.
The above is the core logic of the entire mocking logic. For more implementation details, please refer to the source code. If you have any questions, suggestions, or improvement proposals, you are welcome to participate in the discussion and contribute through Github Issue and Pull Request 😃

View File

@@ -0,0 +1,8 @@
Feedback Channel
---
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.
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

@@ -0,0 +1,62 @@
Frequently Asked Questions
---
#### 1. How to mock the fields initialized by `@Autowired` in the class under test?
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?
`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.
#### 3. Which test frameworks does `TestableMock` support?
The `PrivateAccessor` / `OmniConstructor` / `OmniAccessor` and basic functionality of `TestableMock` are theoretically test-frameworks-independent, and should work with any test frameworks out-of-box.
The only test-framework-related part is the mock invocation verifier, which currently supports `JUnit 4` / `JUnit 5` / `TestNG` and `Spock` appropriately.
If you encounter compatibility problem under a specific test framework, or want to add mock invocation verifier support for any other frameworks, please raise an [issue](https://github.com/alibaba/testable-mock/issues) to tell us.
#### 4. How to implement the mock method when the parent class variable points to the child class object?
In the code, there are often cases of using <u>interface variables or parent class variables</u> to point to an instance of subclasses and calling methods provided by the parent class or subclass.
At this time, follow a principle that the type of the first parameter of the mock method is always the same as the type of the variable that initiated the call.
Therefore, regardless of whether the actually called method comes from the parent class or the subclass, and whether the subclass overrides the method. If the calling variable is of the parent type (or interface type), the first parameter type of the mock method should use the corresponding parent type (or interface) type.
See the use case of the `DemoInheritTest` test class in the Java and Kotlin examples.
#### 5. How to mock generic methods (template methods)?
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.
#### 6. How to mock invocation inside an inner class?
Just put mock methods in the mock container class of its outer class, it works for code in all inner classes.
See the use case of the `DemoInnerClass` test class in the Java and Kotlin examples.
#### 7. Why mocking methods in the `String` class in the Kotlin project does not work?
The `String` type in Kotlin language is actually `kotlin.String` instead of `java.lang.String`. However, when this type is built from bytecode, it will be replaced with Java's `java.lang.String` class, so no matter if the mock target is written as `kotlin.String` or `java.lang.String`, it cannot match the original called method.
In actual scenarios, there are very few scenarios where methods in the `String` class need to be mocked, so `TestableMock` has not dealt with this situation specifically.
#### 8. Can `TestableMock` be used for testing Android projects?
It can be used in combination with [Roboelectric](https://github.com/robolectric/robolectric) testing framework.
The `Dalvik` and `ART` virtual machines of the Android system use a bytecode system different from the standard JVM, which will affect the normal functionality of `TestableMock`. The `Roboelectric` framework can run Android unit tests on a standard JVM virtual machine, which is much faster than running unit tests through the Android virtual machine. Recently, most Android App unit tests are written with the `Roboelectric` framework.
#### 9. Meet "Command Line is too Long. Shorten command line for ..." error when triggering test in IntelliJ IDE?
This problem is caused by the system `Class Path` content is too long, and has nothing to do with `TestableMock`. However, it should be noted that IntelliJ provides two auxiliary solutions: `JAR manifest` and `classpath file`. If `TestableMock` is used in the test, please select `JAR manifest`.
![jar-manifest](https://testable-code.oss-cn-beijing.aliyuncs.com/jar-manifest.png)

View File

@@ -0,0 +1,75 @@
Verify Mock Invocation
---
In the test, in addition to replacing **methods contain external dependencies** with mocks, it is often necessary to verify whether the actual parameters of mock invocation are in accordance with expectations.
The **verifiers** and **matchers** are provided in `TestableMock` to achieve this function. for example:
```java
@Test
public test_case() {
int res = insToTest.methodToTest();
verify("mockMethod").with(123, "abc");
}
```
This use case will check whether the mock method named `mockMethod` has been called when the method under test `methodToTest()` is executed, and whether the parameter values received during the call are `123` and `"abc"` (Assuming that the `mockMethod` method has two parameters).
In addition to this simple verification, `TestableMock` currently supports a variety of **validators**, as well as **matchers** that can fuzzy match parameter characteristics.
The `DemoMatcherTest` test classes in the sample projects `java-demo` and `kotlin-demo` show the usage of these validators and matchers in detail.
## Basic validator
- `with(Object...args)` → verify whether the method has been called by the specified parameters
- `withInOrder(Object... args)` → verify the specified method is called with specified parameters according to the actual calling order
- `withTimes(int expectedCount)` → verify whether the method has been called the specified number of times, ignoring the check of the calling parameters
- `without(Object...args)` → verification method has never been called with specified parameters
- `times(int count)` → use after the `with()` or `withInOrder()` method to verify that the method has been called the specified number of times with the same conditions
## Basic matcher
- `any()` → matches any value, including `null`
- `any(Class<?> clazz)` → match any value of the specified type or subtype
- `anyTypeOf(Class<?>... classes)` → match any type of value in the list
- `anyString()` → matches any string
- `anyNumber()` → matches any number (integer, long, float, double, etc)
- `anyBoolean()` → matches any Boolean value
- `anyByte()` → match any single byte type value
- `anyChar()` → matches any single character type value
- `anyInt()` → matches any integer value
- `anyLong()` → matches any value of long integer type
- `anyFloat()` → match any value of float type
- `anyDouble()` → match any double-precision float number type value
- `anyShort()` → matches any value of short integer type
- `anyArray()` → matches any array
- `anyArrayOf(Class<?> clazz)` → matches any array of the specified type
- `anyList()` → matches any list
- `anyListOf(Class<?> clazz)` → matches any list of the specified type
- `anySet()` → matches any set
- `anySetOf(Class<?> clazz)` → matches any set of the specified type
- `anyMap()` → match any map
- `anyMapOf(Class<?> keyClass, Class<?> valueClass)` → match any map of the specified type
- `anyCollection()` → match any container
- `anyCollectionOf(Class<?> clazz)` → matches any specified type of container
- `anyIterable()` → matches any iterator
- `anyIterableOf(Class<?> clazz)` → matches any iterator of the specified type
- `eq(Object obj)` → match objects equal to the specified value
- `refEq(Object obj)` → match the specified object (not equal to the value, but the same object)
## Null value matcher
- `isNull()` → match `null`
- `notNull()` → matches any value except `null`
- `nullable(Class<?> clazz)` → matches any value of `null` or specified type
## String matcher
- `contains(String substring)` → match a string containing a specific substring
- `matches(String regex)` → match strings that match the specified regular expression
- `endsWith(String suffix)` → match the string ending with the specified substring
- `startsWith(String prefix)` → match the string starting with the specified substring
## Universal matcher
- `any(MatchFunction matcher)` → match the value that matches the specified expression

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,113 @@
Parameter Constructor
---
In unit testing, the preparation and construction of test data is a necessary and tedious task. Object-oriented layer-by-layer encapsulation becomes an obstacle to initializing the state of the object during testing. Especially when the type structure is complicated, there is no suitable construction method, or some fields need to use private inner classes, etc. Using conventional methods to construct those class often appear to be inadequate.
For this reason, `TestableMock` provides two minimalist tool classes, `OmniConstructor` and `OmniAccessor`, which makes the construction of any object no longer difficult.
### 1. Construct any object with one line of code
No matter how special the target type is, `OmniConstructor` will hand it to you immediately~~ The universal object constructor `OmniConstructor` has two static methods:
- `newInstance(<AnyClass>)` ➜ Specify any type, and return an object of that type
- `newArray(<AnyClass>, <ArraySize>)` ➜ Specify any type, and return an array of that type
Usage example:
```java
// Construct a object of "WhatEverClass" type
WhatEverClass obj = OmniConstructor.newInstance(WhatEverClass.class);
// Construct a array of "WhatEverClass[]" type with capability of 5
WhatEverClass[] arr = OmniConstructor.newArray(WhatEverClass.class, 5);
```
Beside that, object constructed by `OmniConstructor` is not just a simple empty object with all member values of `null`, but a "fullness" object in which all members and all sub-members of all members have been recursively initialized. Compared with using `new` operation, `OmniConstructor` can ensure the integrity of the object structure and avoid the `NullPointerException` problem caused by partial initialization of test data.
```java
// Construct object using new operation
Parent parent = new Parent();
// Inner member is not initialized, will cause NullPointerException (❌)
parent.getChild().getGrandChild();
// Construct object using OmniConstructor
Parent parent = OmniConstructor.newInstance(Parent.class);
// No need to worry, visit any child member safely (✅)
parent.getChild().getGrandChild().getContent();
```
> **Note**: In the `0.6.0` version, the member fields of type interface or abstract class will still be initialized to `null`, this problem will be fixed in subsequent versions
In addition to use as input parameters of the method under test, `OmniConstructor` can also be used to quickly construct the return value of the mock method. Compared to using `null` as the return value of the mock method, using a fully initialized object can better guarantee the reliability of the test .
In the `DemoOmniMethodsTest` test class of the `java-demo` and `kotlin-demo` sample projects, it is shown in detail how `OmniConstructor` could be used when the target type has a multi-layered nested structure, the construction method is throwing exception, and even without public construction method available.
### 2. Access any inner member with one line of code
For test data, even with complex structure, it is usually only part of its attributes and states that are related to a specific test case. However, it is sometimes not easy to assign values to these fields deep wrapped in the object structure.
As an enhanced version of the `PrivateAccessor` tool, `OmniAccessor` is inspired by the [XPath node selector](https://www.w3schools.com/xml/xpath_syntax.asp) in the `XML` language, It provides two main static methods of `get` and `set`:
- `get(arbitrary object, "access path")` ➜ returns all member objects searched for based on path-matching
- `set(arbitrary object, "access path", new value)` ➜ Assign a value to any objects based on path-matching
There is also a `getFirst()` method used to directly obtain the unique target object during exact path matching. Its function is equivalent to `OmniAccessor.get(...).get(0)`:
- `getFirst(arbitrary object, "access path")` ➜ returns the first member object searched based on path-matching
You only need to write the access path that meets the rules, no matter what type and depth of members, you can directly reach them with one line of code:
```java
// Get all field of the parent object, which named as content and inside type GrandChild
OmniAccessor.get(parent, "{GrandChild}/content");
// Assign 100 to any fields which named as value and inside any child member that matches the 3rd item of the array named children
OmniAccessor.set(parent, "children[2]/*/value", 100);
```
The path rules are as follows:
**1. Matching member name**
The path name without additional decoration will match any member object with the same name
- `child`: match any descendant member whose name is `child`
- `child/grandChild`: matches the child member named `grandChild` among the descendants of the name `child`
**2. Matching member type**
Use curly braces to match the type name, usually used to obtain or assign multiple member objects of the same type in batches
- `{Child}`: match all descendants of `Child`
- `{Children[]}`: match all descendants of the `Children` array
- `{Child}/{GrandChild}`: match all descendant members of `Child`, all types are children of `GrandChild`
The member name and type can be mixed on the path (currently it is not supported to specify the member name and type at the same time in the same level path)
- `child/{GrandChild}`: match all descendant members whose name is `child`, all types are child members of `GrandChild`
- `{Child}/grandChild/content`: match all descendant members whose type is `Child`, the child members named `grandChild`, and the child members named `content`
**3. Use subscripts to access array members**
Use square brackets with numerical values to indicate that the matching position is an array type, and the object with the specified subscript is taken (without subscript, when the matching object is an array type, all objects in the array are matched)
- `children[1]/content`: match the descendant members of the array type named `children`, and take the child member named `content` in the `2`th object
- `parent/children[1]`: match the child member of the array type named `children` among the descendant members named `parent`, and take the `2`th object among them
**4. Use wildcards**
Wildcards can be used to match member names or type names
- `child*`: match all descendant members whose name starts with `child`
- `{*Child}`: match all descendant members whose type ends with `Child`
- `c*ld/{Grand*ld}`: match the descendant members whose name starts with `c` and ends with `ld`, and the members whose type starts with `Grand` and ends with `ld`
- `child/*/content`: At this time, `*` will match any member, that is, the child member of `content` contained in any child member of the `child` object
For details, see the use cases in the test classes of the `java-demo` and `kotlin-demo` sample projects `DemoOmniMethodsTest`.
### 3. Special instructions
> **Do you really need to use `OmniAccessor`? **
>
> `OmniAccessor` implement the basic anti-refactoring mechanism based on the Fail-Fast principle. When the access path provided by the user cannot match any member, the `OmniAccessor` will immediately throw a `NoSuchMemberError` error, so that the unit test is terminated early. However, compared to the conventional member access method, the support of `OmniAccessor` in IDE refactoring is still weak.
>
> For content assignment of complex objects, in most cases, we recommend using [Builder Pattern](https://www.geeksforgeeks.org/builder-pattern-in-java/), or exposing Getter/Setter method implementations. Although these conventional methods are slightly clumsy (especially when you need to assign values to many similar members in batches), they are more friendly to the encapsulation and reconstruction of business logic.
> Only when the original type is not suitable for transformation, and there is no other way to access the target member, `OmniAccessor` is the last resort.

View File

@@ -0,0 +1,50 @@
Private Accessor
---
Nowadays, the debate about whether private methods should be unit tested is gradually disappearing, and the common practice of developers has given factual answers. Indirect testing of private methods through public methods is difficult in many cases. Developers are more willing to modify method visibility to make original private methods testable in test cases.
In addition, before unit testing begin, it is often necessary to initialize specific member fields of the object under test, but sometimes it could be impossible to easily assign values to these private fields. So, is it possible let the code in the unit test case directly access the private methods and member fields of the class under test without breaking the encapsulation of the type under test? `TestableMock` provides two simple solutions.
### Solution 1: Use `@EnablePrivateAccess` annotation
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)
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 7 static methods:
- `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

@@ -0,0 +1,138 @@
# Release Note
## 0.6.2
- support setup global testable agent configure via properties file
- disable OmniConstructor bytecode enhancement by default
- fix a `Spring` framework compatibility issue with OmniConstructor
## 0.6.1
- generate mock scanning log file automatically, for self-troubleshooting
- fix a `Spock` test framework compatibility issue
- fix a `Gradle` unit test runtime compatibility issue
## 0.6.0
- add `OmniConstructor` and `OmniAccessor` tool for parameter preparation
- fix a `ClassFormatError` caused by incorrect `FRAME FULL` operation
- remove support for`diagnose` parameter of `@MockWith` annotation
## 0.5.2
- support use`PrivateAccessor` to access private member in parent class
- fix a class format error in jvm 1.8+ cause by incorrect bytecode generation
- fix illegal field modifiers error when add mock class to an interface
## 0.5.1
- use kotlin-style method descriptor in `VERBOSE` diagnose logs
- support parameterized test of JUnit 5
- fix an exception caused by method parameter with ternary operator
- fix a bug cause log message lost when `@MockWith` annotation used
## 0.4.12
- support verbose diagnose log for better self-troubleshooting
- support disable private access target existence check
- support specify mock scanning packages
- fix an ArrayIndexOutOfBoundsException issue when transforming native method
## 0.4.11
- support accessing private members of class under test in different package path
- validate the number of private method parameters accessed by `PrivateAccessor`
- fix a bug which may cause errors when the mock method contains array parameters
- fix an issue which cause some private members not be found in the IntelliJ build
## 0.4.10
- fix an issue of using mock in lambda expression
- fix the NullPointerException when invoke private method with parameter value `null`
## 0.4.9
- fix an issue cause by improperly bytecode processing while using `targetClass` parameter
- auto validate access target of `PrivateAccessor`, improve resistance to code refactoring
## 0.4.8
- fix an issue of private method invocation failed in assignment statements
- support using mock for testing with `SpringRunner`
- support `targetClass` parameter in `@MockMethod` annotation
## 0.4.7
- fix incorrect stack size caused by `MOCK_CONTEXT` variable initialization
- fix incorrect gradle build path of under Windows operating system
## 0.4.6
- fix an issue of `IINC` bytecode processing
- support `TestableTool.MOCK_CONTEXT` variable to inject extra parameters to mock context
- use `TestableTool.TEST_CASE` variable to distinguish test case is no longer recommended
## 0.4.5
- fix private access compile error in intelliJ 2020.3+ environment
- change javaagent initialization logic to avoid NPE in cross-layer test scenario
- support dump transformed byte code to local file for issue investigation
## 0.4.4
- fix an issue of accessing private method with interface type parameter fail
- fix an issue of mocking static method without parameter fail
## 0.4.3
- support static private member access
## 0.4.2
- support change javaagent global log level via maven plugin
- fix an issue of duplicate test class injection
- fix an issue cause multiple-line method invocation mock fail
## 0.4.1
- deprecate @TestableMock annotation, use @MockMethod and @MockConstructor instead
## 0.4.0
- fix a jvm 9+ compatibility issue cause by default classloader change
- fix a conflict issue when testcase name duplicated between different class
- refactor code structure, module `testable-all` added
- separate constant definition from `TestableTool` to `TestableConst`
## 0.3.2
- support gradle project for both private member access and quick mock
- support access private static field and methods via PrivateAccessor
## 0.3.1
- support detail log of mocking process for diagnosis
## v0.3.0
- add `without()` checker to verify mock method never invoked with specified parameters
- support fuzz matcher when verifying mock invocation
## v0.2.2
- support mock method parameters check
- fix a compatibility issue with jvm 9+
## v0.2.1
- support mock static method
- support mock kotlin companion object method
- 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`
- rename annotations to reflect the actual use
## v0.1.0
- move generated agent jar to class folder
- support mock method of any object
## v0.0.5
- use dynamically runtime modification to replace static `e.java` file
- get rid of unit test framework dependence
- add testable ref field in test class at runtime instead of compile time
## v0.0.4
- use runtime byte code rewrite to invoke testable setup method
- add `TestableUtil` class to fetch current test case and invocation source
## v0.0.3
- use global method invoke to access private members instead of modification in place
- use `e.java` replace `testable` class make code more readable
- introduce `agent` module, use runtime byte code modification to support new operation and member method mocking
## v0.0.2
- add support of member method mocking by compile time code modification
## v0.0.1
- PoC version
- use compile time code modification to support new operation mocking and private field & method access

View File

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

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