├── .github
└── workflows
│ └── maven.yml
├── LICENSE.txt
├── README.MD
├── pom.xml
└── src
├── main
├── java
│ └── com
│ │ └── teketik
│ │ └── test
│ │ └── mockinbean
│ │ ├── BeanFieldState.java
│ │ ├── BeanUtils.java
│ │ ├── Definition.java
│ │ ├── FieldState.java
│ │ ├── InBeanDefinition.java
│ │ ├── InBeanDefinitionsParser.java
│ │ ├── MockDefinition.java
│ │ ├── MockInBean.java
│ │ ├── MockInBeanTestExecutionListener.java
│ │ ├── MockInBeans.java
│ │ ├── ProxiedBeanFieldState.java
│ │ ├── SpyDefinition.java
│ │ ├── SpyInBean.java
│ │ ├── SpyInBeans.java
│ │ └── TestFieldState.java
└── resources
│ └── META-INF
│ └── spring.factories
└── test
└── java
└── com
└── teketik
└── test
└── mockinbean
├── BeanUtils_FindBeanTest.java
├── BeanUtils_findFieldTest.java
├── MockInBeanTestExecutionListenerBridge.java
├── data
├── Customer.java
├── CustomerRepository.java
├── CustomerService.java
├── CustomerServiceMockTest.java
└── DataApplication.java
└── test
├── AssertingCleanTestExecutionListener.java
├── BaseTest.java
├── ComponentWithDifferentNameTest.java
├── GenericComponentTest.java
├── InterfaceImplementation1Test.java
├── InterfaceImplementation2Test.java
├── InterfaceImplementationTestConfig.java
├── InvalidBeanNameResolvedByTypeTest.java
├── InvalidBeanNameUnResolvedTest.java
├── MockInBeanBaseTest.java
├── MockInBeanMetaAnnotationsTest.java
├── MockInBeanTest.java
├── MockInSpyTest.java
├── MultipleComponentsOfSameTypeTest.java
├── MultipleComponentsThroughInheritanceTest.java
├── MultipleMockDefinitionForAFieldTest.java
├── NestedTest.java
├── NestedTopLevelTest.java
├── ProxyTest.java
├── SpyInBeanTest.java
├── TestApplication.java
├── TestInheritance1Test.java
├── TestInheritance2Test.java
├── TestInheritanceBaseTest.java
├── TestInterface.java
├── TestUtils.java
├── VerifyAdvisedSpyInBeanTest.java
├── annotations
├── MockInBeanWithCustomName.java
└── MockInMultipleComponents.java
├── components
├── GenericMockableComponent.java
├── GenericTestComponent.java
├── InterceptedComponent.java
├── InterceptedComponentAspect.java
├── MockableComponent1.java
├── MockableComponent2.java
├── MockableComponentExtending2.java
├── TestComponent1.java
├── TestComponent2.java
├── TestComponent3.java
├── TestComponentBase.java
├── TestComponentInterface.java
├── TestComponentInterfaceImpl.java
├── TestComponentOn3.java
└── TestComponentWithMultipleSameType.java
└── example
├── ExpensiveProcessor.java
├── MyService.java
├── MyServiceTest.java
└── ThirdPartyApiService.java
/.github/workflows/maven.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
3 |
4 | name: Java CI with Maven
5 |
6 | on: push
7 |
8 | jobs:
9 | build:
10 |
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v2
15 | - name: Set up JDK 1.8
16 | uses: actions/setup-java@v1
17 | with:
18 | java-version: 1.8
19 | - name: Build with Maven
20 | run: mvn -B package --file pom.xml
21 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # Mock in Bean
2 |
3 | [@MockInBean](src/main/java/com/teketik/test/mockinbean/MockInBean.java) and [@SpyInBean](src/main/java/com/teketik/test/mockinbean/SpyInBean.java) are alternatives to @MockBean and @SpyBean for Spring Boot tests *(>= 2.6.15 including >= 3.X.X)*.
4 |
5 | They surgically replace a field value in a Spring Bean by a Mock/Spy for the duration of a test and set back the original value afterwards, leaving the Spring Context clean.
6 |
7 | **@MockInBean 'mocks a bean in a bean'** whereas **@MockBean 'mocks a bean in the whole context'**.
8 |
9 | # But why ?
10 |
11 | ## The problem:
12 |
13 | Spring Context pollution was a fairly common problem before the introduction of @MockBean and @SpyBean. Many developers would inject mocks in beans during tests through `@InjectMock` or using manual setters and often forget to set back the original field values which would leave the Spring context polluted and cause test failures in unrelated tests.
14 |
15 | @MockBean and @SpyBean solved this issue by providing Mockito injection directly in the Spring Context but introduced an undesirable side-effect: their usage dirties the context and may lead to the re-creation of new Spring contexts for any unique combination, which can be incredibly time-consuming. See [The Problems with @MockBean](https://www.baeldung.com/spring-tests#2-the-problems-withmockbean)
16 |
17 | ### The solution:
18 |
19 | Assuming you really need to run the test in the Spring Context, the most straight-forward solution is still to inject your mock/spy in your bean and reset it afterwards.
20 |
21 | **@MockInBean and @SpyInBean brings the convenience of @MockBean and @SpyBean and do just that:**
22 | 1. Set the mock/spy in the bean.
23 | 2. Replace the mock/spy by the original value afterwards.
24 |
25 | # Example:
26 |
27 | Assuming that you want to test the following service:
28 | ```java
29 | @Service
30 | public class MyService {
31 |
32 | @Autowired
33 | protected ThirdPartyApiService thirdPartyService;
34 |
35 | @Autowired
36 | protected ExpensiveProcessor expensiveProcessor;
37 |
38 | public void doSomething() {
39 | final Object somethingExpensive = expensiveProcessor.returnSomethingExpensive();
40 | thirdPartyService.doSomethingOnThirdPartyApi(somethingExpensive);
41 | }
42 |
43 | }
44 |
45 | ```
46 |
47 | You can write your test this way:
48 | ```java
49 | @SpringBootTest
50 | public class MyServiceTest {
51 |
52 | @MockInBean(MyService.class)
53 | private ThirdPartyApiService thirdPartyApiService;
54 |
55 | @SpyInBean(MyService.class)
56 | private ExpensiveProcessor expensiveProcessor;
57 |
58 | @Autowired
59 | private MyService myService;
60 |
61 | @Test
62 | public void test() {
63 | final Object somethingExpensive = new Object();
64 | Mockito.when(expensiveProcessor.returnSomethingExpensive()).thenReturn(somethingExpensive);
65 | myService.doSomething();
66 | Mockito.verify(thirdPartyApiService).doSomethingOnThirdPartyApi(somethingExpensive);
67 | }
68 |
69 | }
70 | ```
71 |
72 | **What happens**:
73 |
74 | Before each test:
75 | - `MyServiceTest.thirdPartyService` will be created as a mock and injected in the target of @MockInBean: `MyService`.
76 | - `MyServiceTest.expensiveProcessor` will be created as a spy of the bean `expensiveProcessor` and injected in the target of @SpyInBean: `MyService`.
77 |
78 | After the tests of `MyServiceTest` are done:
79 | - `MyService.thirdPartyService` will be reset to the original Spring bean `thirdPartyService`
80 | - `MyService.expensiveProcessor` will be reset to the original Spring bean `expensiveProcessor`
81 |
82 | # Usage:
83 |
84 | Simply include the maven dependency (from central maven) to start using @MockInBean and @SpyInBean in your tests.
85 | ```
86 |
87 | com.teketik
88 | mock-in-bean
89 | boot2-v1.9
90 | test
91 |
92 | ```
93 |
94 | @MockInBean and @SpyInBean also support:
95 | - Injection in multiple Spring beans: *Repeat the annotation on your field*.
96 | - Injection in bean identified by name if multiple instances exist in the context: *Specify a `name` in your annotation*.
97 |
98 | Checkout the javadoc for more information.
99 |
100 | # Limitations:
101 |
102 | This approach has some limitations compared to the @MockBean/@SpyBean equivalent:
103 | - There is currently no isolation per thread. It is not advised to use this library in parallel test suites. (Parallel test execution is supported experimentally in https://github.com/antoinemeyer/mock-in-bean/tree/v2.1-RC).
104 | - Operations on beans happening within another bean's constructor will not be performed against the mocks since the mocks are injected directly into the fields. Do not use @MockInBean/@SpyInBean for beans that are manipulated in constructors.
105 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 | 4.0.0
6 | com.teketik
7 | mock-in-bean
8 | boot2-v1.9
9 | Mock in Bean
10 | Surgically Inject Mockito Mock/Spy in Spring Beans
11 | https://github.com/antoinemeyer/mock-in-bean
12 |
13 | https://github.com/antoinemeyer/mock-in-bean
14 | scm:git:git://github.com/antoinemeyer/mock-in-bean.git
15 | scm:git:git@github.com:antoinemeyer/mock-in-bean.git
16 |
17 |
18 |
19 |
20 | The Apache License, Version 2.0
21 | http://www.apache.org/licenses/LICENSE-2.0.txt
22 |
23 |
24 |
25 |
26 |
27 | Antoine Meyer
28 |
29 |
30 |
31 |
32 | 1.8
33 | 1.8
34 |
35 |
36 |
37 | org.springframework.boot
38 | spring-boot-dependencies
39 | 2.6.15
40 |
41 |
42 |
43 |
44 |
45 | org.springframework.boot
46 | spring-boot-test
47 |
48 |
49 | org.springframework
50 | spring-test
51 |
52 |
53 | org.mockito
54 | mockito-core
55 |
56 |
57 | org.junit.jupiter
58 | junit-jupiter-api
59 |
60 |
61 |
62 |
63 | org.springframework.boot
64 | spring-boot-starter-aop
65 | test
66 |
67 |
68 | org.springframework.boot
69 | spring-boot-starter-test
70 | test
71 |
72 |
73 | org.springframework.boot
74 | spring-boot-starter-data-jpa
75 | test
76 |
77 |
78 | com.h2database
79 | h2
80 | test
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | org.apache.maven.plugins
89 | maven-source-plugin
90 |
91 |
92 | attach-sources
93 |
94 | jar
95 |
96 |
97 |
98 |
99 |
100 | org.apache.maven.plugins
101 | maven-javadoc-plugin
102 |
103 |
104 | attach-javadocs
105 |
106 | jar
107 |
108 |
109 |
110 |
111 |
112 | org.apache.maven.plugins
113 | maven-gpg-plugin
114 |
115 |
116 | sign-artifacts
117 | verify
118 |
119 | sign
120 |
121 |
122 |
123 |
124 |
125 | org.sonatype.plugins
126 | nexus-staging-maven-plugin
127 | true
128 |
129 | ossrh
130 | https://oss.sonatype.org/
131 | true
132 |
133 |
134 |
135 |
136 |
137 |
--------------------------------------------------------------------------------
/src/main/java/com/teketik/test/mockinbean/BeanFieldState.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean;
2 |
3 | import org.springframework.test.context.TestContext;
4 | import org.springframework.util.ReflectionUtils;
5 |
6 | import java.lang.reflect.Field;
7 |
8 | class BeanFieldState extends FieldState {
9 |
10 | final Object bean;
11 |
12 | final Object originalValue;
13 |
14 | public BeanFieldState(Object bean, Field field, Object originalValue, Definition definition) {
15 | super(field, definition);
16 | this.bean = bean;
17 | this.originalValue = originalValue;
18 | }
19 |
20 | @Override
21 | public Object resolveTarget(TestContext testContext) {
22 | return bean;
23 | }
24 |
25 | public void rollback(TestContext testContext) {
26 | final Object target = resolveTarget(testContext);
27 | ReflectionUtils.setField(field, target, originalValue);
28 | }
29 |
30 | public Object createMockOrSpy() {
31 | return definition.create(originalValue);
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/teketik/test/mockinbean/BeanUtils.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean;
2 |
3 | import org.springframework.aop.TargetSource;
4 | import org.springframework.aop.framework.Advised;
5 | import org.springframework.aop.framework.AopProxyUtils;
6 | import org.springframework.aop.support.AopUtils;
7 | import org.springframework.context.ApplicationContext;
8 | import org.springframework.lang.Nullable;
9 | import org.springframework.util.Assert;
10 | import org.springframework.util.ReflectionUtils;
11 |
12 | import java.lang.reflect.Field;
13 | import java.util.Map;
14 | import java.util.Map.Entry;
15 |
16 | abstract class BeanUtils {
17 |
18 | private BeanUtils() {
19 | }
20 |
21 | /**
22 | *
Attempt to find the spring bean corresponding to {@code type} if only one exists ({@code name} not used).
23 | * If multiple exist for that {@code type}, return the one with the corresponding {@code name}.
24 | * @param
25 | * @param type
26 | * @param name may be null
27 | * @param applicationContext
28 | * @return the bean, if found
29 | */
30 | static T findBean(Class type, @Nullable String name, ApplicationContext applicationContext) {
31 | final Map beansOfType = applicationContext.getBeansOfType(type);
32 | Assert.isTrue(!beansOfType.isEmpty(), () -> "No beans of type " + type);
33 | final T beanOrProxy;
34 | if (beansOfType.size() == 1) {
35 | beanOrProxy = beansOfType
36 | .values()
37 | .iterator()
38 | .next();
39 | } else {
40 | Assert.notNull(name, () -> "Multiple beans of type " + type + ". A name must be provided");
41 | beanOrProxy = beansOfType
42 | .entrySet()
43 | .stream()
44 | .filter(e -> e.getKey().equalsIgnoreCase(name))
45 | .map(Entry::getValue)
46 | .findFirst()
47 | .orElseThrow(() -> new IllegalArgumentException("No beans of type " + type + " and name " + name));
48 | }
49 | return AopUtils.isAopProxy(beanOrProxy)
50 | ? (T) AopProxyUtils.getSingletonTarget(beanOrProxy)
51 | : beanOrProxy;
52 | }
53 |
54 | /**
55 | * Attempt to find a {@link Field field} on the supplied {@link Class class} with the
56 | * supplied {@code name} and {@code type} OR just the {@code type} if none with this {@code name} exists.
57 | *
Searches all superclasses up to {@link Object}.
58 | * @param clazz
59 | * @param name
60 | * @param type
61 | * @return the field, if found.
62 | */
63 | static Field findField(Class> clazz, @Nullable String name, Class> type) {
64 | final Object[] results = new Object[2]; //name+type as 0, type only as [1]
65 | ReflectionUtils.doWithFields(clazz, field -> {
66 | if (name != null && field.getName().equalsIgnoreCase(name)) {
67 | if (results[0] == null) {
68 | results[0] = field;
69 | } else {
70 | results[0] = Boolean.FALSE; //multiple matching fields
71 | }
72 | }
73 | if (results[1] == null) {
74 | results[1] = field;
75 | } else {
76 | results[1] = Boolean.FALSE; //multiple matching fields
77 | }
78 | }, field -> field.getType().isAssignableFrom(type));
79 | if (results[0] != null) {
80 | Assert.isTrue(
81 | !(results[0] instanceof Boolean),
82 | () -> "Multiple fields of type " + type + " in " + clazz + " with name " + name
83 | );
84 | return (Field) results[0];
85 | }
86 | if (results[1] != null) {
87 | Assert.isTrue(
88 | !(results[1] instanceof Boolean),
89 | () -> "Multiple fields of type " + type + " in " + clazz
90 | + (
91 | name != null
92 | ? " and none with name " + name
93 | : ". Please specify a name."
94 | )
95 | );
96 | return (Field) results[1];
97 | }
98 | return null;
99 | }
100 |
101 | static @Nullable TargetSource getProxyTarget(Object candidate) {
102 | try {
103 | while (AopUtils.isCglibProxy(candidate) && candidate instanceof Advised) {
104 | Advised advised = (Advised) candidate;
105 | TargetSource targetSource = advised.getTargetSource();
106 |
107 | if (targetSource.isStatic()) {
108 | Object target = targetSource.getTarget();
109 |
110 | if (target == null || !AopUtils.isAopProxy(target)) {
111 | return targetSource;
112 | }
113 | candidate = target;
114 | } else {
115 | return null;
116 | }
117 | }
118 | } catch (Throwable ex) {
119 | throw new IllegalStateException("Failed to unwrap proxied object", ex);
120 | }
121 | return null;
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/src/main/java/com/teketik/test/mockinbean/Definition.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean;
2 |
3 | import org.mockito.Mock;
4 | import org.mockito.Spy;
5 | import org.springframework.core.ResolvableType;
6 |
7 | import java.util.Objects;
8 |
9 | /**
10 | *
Definition of a {@link Mock mock} or a {@link Spy spy}.
11 | *
Corresponding entity can be created using {@link #create(Object)}.
12 | * @author Antoine Meyer
13 | */
14 | abstract class Definition {
15 |
16 | protected final String name;
17 | protected final ResolvableType resolvableType;
18 |
19 | Definition(String name, ResolvableType resolvableType) {
20 | super();
21 | this.name = name;
22 | this.resolvableType = resolvableType;
23 | }
24 |
25 | String getName() {
26 | return name;
27 | }
28 |
29 | ResolvableType getResolvableType() {
30 | return resolvableType;
31 | }
32 |
33 | /**
34 | * Creates a mock or a spy of the provided original value.
35 | * @param
36 | * @param originalValue
37 | * @return
38 | */
39 | abstract T create(Object originalValue);
40 |
41 | @Override
42 | public int hashCode() {
43 | return Objects.hash(name, resolvableType);
44 | }
45 |
46 | @Override
47 | public boolean equals(Object obj) {
48 | if (this == obj) {
49 | return true;
50 | }
51 | if (obj == null) {
52 | return false;
53 | }
54 | if (getClass() != obj.getClass()) {
55 | return false;
56 | }
57 | Definition other = (Definition) obj;
58 | return Objects.equals(name, other.name) && Objects.equals(resolvableType, other.resolvableType);
59 | }
60 |
61 | @Override
62 | public String toString() {
63 | return "Definition [name=" + name + ", resolvableType=" + resolvableType + "]";
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/com/teketik/test/mockinbean/FieldState.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean;
2 |
3 | import org.springframework.test.context.TestContext;
4 |
5 | import java.lang.reflect.Field;
6 |
7 | abstract class FieldState {
8 |
9 | final Field field;
10 |
11 | final Definition definition;
12 |
13 | public FieldState(Field targetField, Definition definition) {
14 | this.field = targetField;
15 | this.definition = definition;
16 | }
17 |
18 | abstract Object resolveTarget(TestContext testContext);
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/teketik/test/mockinbean/InBeanDefinition.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean;
2 |
3 | import org.springframework.lang.Nullable;
4 |
5 | /**
6 | * Defines a recipient of a {@link MockDefinition} or {@link SpyDefinition} extracted from {@link MockInBean} or {@link SpyInBean}.
7 | * @author Antoine Meyer
8 | */
9 | class InBeanDefinition {
10 |
11 | final Class> clazz;
12 |
13 | @Nullable
14 | final String name;
15 |
16 | InBeanDefinition(Class> clazz, String name) {
17 | super();
18 | this.clazz = clazz;
19 | this.name = name;
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/teketik/test/mockinbean/InBeanDefinitionsParser.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean;
2 |
3 | import org.springframework.core.ResolvableType;
4 | import org.springframework.core.annotation.MergedAnnotation;
5 | import org.springframework.core.annotation.MergedAnnotations;
6 | import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
7 | import org.springframework.util.Assert;
8 | import org.springframework.util.ReflectionUtils;
9 | import org.springframework.util.StringUtils;
10 |
11 | import java.lang.reflect.AnnotatedElement;
12 | import java.lang.reflect.Field;
13 | import java.lang.reflect.TypeVariable;
14 | import java.util.Collections;
15 | import java.util.HashMap;
16 | import java.util.LinkedHashSet;
17 | import java.util.LinkedList;
18 | import java.util.List;
19 | import java.util.Map;
20 | import java.util.Set;
21 |
22 | /**
23 | * Similar to {@link org.springframework.boot.test.mock.mockito.DefinitionsParser} but handles {@link MockInBean} and {@link SpyInBean}.
24 | *
Every mock/spy {@link Definition} maps to one or more {@link InBeanDefinition}.
25 | * @see DefinitionsParser
26 | */
27 | class InBeanDefinitionsParser {
28 |
29 | private final Map> definitions = new HashMap>();
30 |
31 | void parse(Class> source) {
32 | ReflectionUtils.doWithFields(source, (field) -> parseField(field, source));
33 | }
34 |
35 | private void parseField(Field element, Class> source) {
36 | final MergedAnnotations annotations = MergedAnnotations.from(element, SearchStrategy.SUPERCLASS);
37 | annotations
38 | .stream(MockInBean.class)
39 | .map(MergedAnnotation::synthesize)
40 | .forEach((annotation) -> parseMockInBeanAnnotation(annotation, element, source));
41 | annotations
42 | .stream(SpyInBean.class)
43 | .map(MergedAnnotation::synthesize)
44 | .forEach((annotation) -> parseSpyInBeanAnnotation(annotation, element, source));
45 | }
46 |
47 | private void parseMockInBeanAnnotation(MockInBean annotation, Field field, Class> source) {
48 | final Set typesToMock = getOrDeduceTypes(field, source);
49 | Assert.state(!typesToMock.isEmpty(), () -> "Unable to deduce type to mock from " + field);
50 | for (ResolvableType typeToMock : typesToMock) {
51 | final Definition definition = new MockDefinition(
52 | field.getName(),
53 | typeToMock
54 | );
55 | final InBeanDefinition inBeanDefinition = new InBeanDefinition(
56 | annotation.value(),
57 | StringUtils.isEmpty(annotation.name()) ? null : annotation.name()
58 | );
59 | addDefinition(definition, inBeanDefinition);
60 | }
61 | }
62 |
63 | private void parseSpyInBeanAnnotation(SpyInBean annotation, Field field, Class> source) {
64 | final Set typesToSpy = getOrDeduceTypes(field, source);
65 | Assert.state(!typesToSpy.isEmpty(), () -> "Unable to deduce type to spy from " + field);
66 | for (ResolvableType typeToSpy : typesToSpy) {
67 | final Definition definition = new SpyDefinition(
68 | field.getName(),
69 | typeToSpy
70 | );
71 | final InBeanDefinition inBeanDefinition = new InBeanDefinition(
72 | annotation.value(),
73 | StringUtils.isEmpty(annotation.name()) ? null : annotation.name()
74 | );
75 | addDefinition(definition, inBeanDefinition);
76 | }
77 | }
78 |
79 | private void addDefinition(Definition definition, InBeanDefinition inBeanDefinition) {
80 | List inBeanBaseDefinitions = definitions.get(definition);
81 | if (inBeanBaseDefinitions == null) {
82 | inBeanBaseDefinitions = new LinkedList();
83 | definitions.put(definition, inBeanBaseDefinitions);
84 | }
85 | inBeanBaseDefinitions.add(inBeanDefinition);
86 | }
87 |
88 | private Set getOrDeduceTypes(AnnotatedElement element, Class> source) {
89 | Set types = new LinkedHashSet<>();
90 | if (types.isEmpty() && element instanceof Field) {
91 | Field field = (Field) element;
92 | types.add(
93 | (field.getGenericType() instanceof TypeVariable)
94 | ? ResolvableType.forField(field, source)
95 | : ResolvableType.forField(field)
96 | );
97 | }
98 | return types;
99 | }
100 |
101 | public Map> getDefinitions() {
102 | return Collections.unmodifiableMap(definitions);
103 | }
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/src/main/java/com/teketik/test/mockinbean/MockDefinition.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean;
2 |
3 | import static org.mockito.Mockito.mock;
4 |
5 | import org.mockito.MockSettings;
6 | import org.springframework.boot.test.mock.mockito.MockReset;
7 | import org.springframework.core.ResolvableType;
8 |
9 | class MockDefinition extends Definition {
10 |
11 | MockDefinition(String name, ResolvableType type) {
12 | super(name, type);
13 | }
14 |
15 | @Override
16 | T create(Object originalValue) {
17 | MockSettings settings = MockReset.withSettings(MockReset.AFTER);
18 | settings.name(name);
19 | return (T) mock(resolvableType.resolve(), settings);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/teketik/test/mockinbean/MockInBean.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean;
2 |
3 | import org.mockito.Mock;
4 | import org.springframework.boot.test.mock.mockito.MockBean;
5 |
6 | import java.lang.annotation.ElementType;
7 | import java.lang.annotation.Repeatable;
8 | import java.lang.annotation.Retention;
9 | import java.lang.annotation.RetentionPolicy;
10 | import java.lang.annotation.Target;
11 |
12 | /**
13 | * Annotation used to inject a mockito {@link Mock} in a Spring Bean for the duration of a test.
14 | *
This is a convenient alternative to {@link MockBean @MockBean} that provides surgical mock injection without dirtying or polluting the Spring context:
15 | * Mocks are injected for every test method and the original Spring Beans(s) are re-injected after the test class is done.
16 | *
17 | * Example:
18 | * Assuming you have the following service:
19 | *
20 | * @Service
21 | * public class MyService {
22 | *
23 | * @Autowired
24 | * private ThirdPartyApi thirdPartyApi;
25 | *
26 | * public Object returnSomethingFromThirdPartyApi() {
27 | * return thirdPartyApi.returnSomething();
28 | * }
29 | * }
30 | *
31 | * You can create a test for your service with a mocked {@code ThirdPartyApi} like:
32 | *
33 | * @SpringBootTest
34 | * public class MyServiceTest {
35 | *
36 | * @MockInBean(MyService.class)
37 | * private ThirdPartyApi thirdPartyApi;
38 | *
39 | * @Autowired
40 | * private MyService myService;
41 | *
42 | * @Test
43 | * public void test() {
44 | * final Object expected = new Object();
45 | * Mockito.when(thirdPartyApi.returnSomething()).thenReturn(expected);
46 | * final Object actual = myService.returnSomethingFromThirdPartyApi();
47 | * Assert.assertEquals(expected, actual);
48 | * }
49 | * }
50 | *
51 | * {@code thirdPartyApi} will be a {@link Mock} that is recreated for every test method in {@code MyServiceTest} and {@code MyService}.
52 | * The original {@code ThirdPartyApi} Spring bean will be re-injected in {@code MyService} after all the tests of {@code MyServiceTest}.
53 | *
54 | * In case the bean in which you are trying to inject a mock has multiple instances registered in the context, you can specify the {@link #name() name} of the bean:
55 | *
56 | * @MockInBean(value = MyService.class, name = "nameOfMyService")
57 | * private ThirdPartyApi thirdPartyApi;
58 | *
59 | *
60 | * You can also inject your mock in multiple beans by repeating the annotation:
61 | *
62 | * @MockInBean(MyFirstService.class),
63 | * @MockInBean(MySecondService.class)
64 | * private ThirdPartyApi thirdPartyApi;
65 | *
66 | * @author Antoine Meyer
67 | * @see SpyInBean
68 | */
69 | @Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
70 | @Retention(RetentionPolicy.RUNTIME)
71 | @Repeatable(MockInBeans.class)
72 | public @interface MockInBean {
73 |
74 | /**
75 | * @return the {@code class} of the Spring Bean in which you want your {@link Mock} to be injected for the duration of the test.
76 | */
77 | Class> value();
78 |
79 | /**
80 | * @return the {@code name} of the Spring Bean in which you want your {@link Mock} to be injected for the duration of the test.
81 | * This is not necessary and is only useful if your context contains multiple Beans of the same type with different names.
82 | */
83 | String name() default "";
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/com/teketik/test/mockinbean/MockInBeanTestExecutionListener.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean;
2 |
3 | import org.junit.jupiter.api.Nested;
4 | import org.mockito.Mock;
5 | import org.mockito.Spy;
6 | import org.springframework.aop.TargetSource;
7 | import org.springframework.core.annotation.AnnotationUtils;
8 | import org.springframework.test.context.TestContext;
9 | import org.springframework.test.context.TestExecutionListener;
10 | import org.springframework.test.context.support.AbstractTestExecutionListener;
11 | import org.springframework.util.Assert;
12 | import org.springframework.util.ReflectionUtils;
13 |
14 | import java.lang.reflect.Field;
15 | import java.lang.reflect.ParameterizedType;
16 | import java.lang.reflect.Type;
17 | import java.util.HashMap;
18 | import java.util.HashSet;
19 | import java.util.IdentityHashMap;
20 | import java.util.LinkedList;
21 | import java.util.List;
22 | import java.util.Map;
23 | import java.util.Map.Entry;
24 | import java.util.Optional;
25 | import java.util.Set;
26 | import java.util.concurrent.ConcurrentHashMap;
27 |
28 | /**
29 | * {@link TestExecutionListener} handling the creation and injection of {@link Mock}s and {@link Spy}s in the test classes.
30 | *
New {@link Mock}s and {@link Spy}s are created and injected {@link #beforeTestMethod(TestContext)}.
31 | *
Original Spring Beans are re-injected in the related Spring Beans {@link #afterTestClass(TestContext)}.
32 | * @author Antoine Meyer
33 | */
34 | class MockInBeanTestExecutionListener extends AbstractTestExecutionListener {
35 |
36 | private static final String ORIGINAL_VALUES_ATTRIBUTE_NAME = "MockInBean.originalValues";
37 |
38 | private static final Map, TestContext> ROOT_TEST_CONTEXT_TRACKER = new ConcurrentHashMap<>(new HashMap<>());
39 |
40 | /*
41 | * Extracts the mock and spy bean definitions.
42 | * Visit all the definitions to capture the original values along with the definitions.
43 | * Ensures no more than one mock/spy definition per field.
44 | */
45 | @Override
46 | public void beforeTestClass(TestContext testContext) throws Exception {
47 | if (isNestedTestClass(testContext.getTestClass())) {
48 | return;
49 | }
50 | ROOT_TEST_CONTEXT_TRACKER.put(testContext.getTestClass(), testContext);
51 | final InBeanDefinitionsParser parser = new InBeanDefinitionsParser();
52 | final Class> targetTestClass = resolveTestClass(testContext.getTestClass());
53 | parser.parse(targetTestClass);
54 | final Set visitedFields = new HashSet<>();
55 | final LinkedList originalValues = new LinkedList<>();
56 | for (Entry> definitionToInbeans : parser.getDefinitions().entrySet()) {
57 | final Definition definition = definitionToInbeans.getKey();
58 | final Class> mockOrSpyType = extractClass(definition);
59 | Field beanField = null;
60 | for (InBeanDefinition inBeanDefinition : definitionToInbeans.getValue()) {
61 | final Object inBean = BeanUtils.findBean(inBeanDefinition.clazz, inBeanDefinition.name, testContext.getApplicationContext());
62 | beanField = BeanUtils.findField(inBean.getClass(), definition.getName(), mockOrSpyType);
63 | Assert.notNull(beanField, "Cannot find any field for definition:" + definitionToInbeans.getKey());
64 | beanField.setAccessible(true);
65 | final Object beanFieldValue = ReflectionUtils.getField(beanField, inBean);
66 | final TargetSource proxyTarget = BeanUtils.getProxyTarget(beanFieldValue);
67 | final BeanFieldState beanFieldState;
68 | if (proxyTarget != null) {
69 | beanFieldState = new ProxiedBeanFieldState(inBean, beanField, beanFieldValue, proxyTarget, definition);
70 | } else {
71 | beanFieldState = new BeanFieldState(inBean, beanField, beanFieldValue, definition);
72 | }
73 | originalValues.add(beanFieldState);
74 | }
75 | Assert.isTrue(visitedFields.add(beanField), beanField + " can only be mapped once, as a mock or a spy, not both!");
76 | final Field testField = ReflectionUtils.findField(targetTestClass, definition.getName(), mockOrSpyType);
77 | testField.setAccessible(true);
78 | originalValues.add(new TestFieldState(testField, definition));
79 | }
80 | testContext.setAttribute(ORIGINAL_VALUES_ATTRIBUTE_NAME, originalValues);
81 | super.beforeTestClass(testContext);
82 | }
83 |
84 | /*
85 | * Iterate over all the definitions and create a corresponding mock/spy that is injected in the beans and the test class
86 | */
87 | @Override
88 | public void beforeTestMethod(TestContext testContext) throws Exception {
89 | final TestContext applicableTestContext = ROOT_TEST_CONTEXT_TRACKER
90 | .get(resolveTestClass(testContext.getTestClass()));
91 | final Map mockOrSpys = new HashMap<>();
92 | final LinkedList fieldStates = (LinkedList) applicableTestContext.getAttribute(ORIGINAL_VALUES_ATTRIBUTE_NAME);
93 | final Map spyTracker = new IdentityHashMap<>();
94 | //First loop to setup all the mocks and spies
95 | fieldStates
96 | .stream()
97 | .filter(BeanFieldState.class::isInstance)
98 | .map(BeanFieldState.class::cast)
99 | .forEach(fieldState -> {
100 | Object mockOrSpy = mockOrSpys.get(fieldState.definition);
101 | if (mockOrSpy == null) {
102 | mockOrSpy = fieldState.createMockOrSpy();
103 | mockOrSpys.put(fieldState.definition, mockOrSpy);
104 | if (fieldState.definition instanceof SpyDefinition) {
105 | spyTracker.put(fieldState.originalValue, mockOrSpy);
106 | }
107 | }
108 | });
109 | //Second loop to process the injections (handling mocks in spies)
110 | fieldStates
111 | .forEach(fieldState -> {
112 | final Object mockOrSpy = mockOrSpys.get(fieldState.definition);
113 | final Object bean = fieldState.resolveTarget(applicableTestContext);
114 | //inject in original bean
115 | inject(fieldState.field, bean, mockOrSpy);
116 | //if the target bean has been spied on, need to push into this spy as well (to allow mock in spies)
117 | Optional.ofNullable(spyTracker.get(bean))
118 | .ifPresent(spy ->inject(fieldState.field, spy, mockOrSpy));
119 |
120 | });
121 |
122 | super.beforeTestMethod(testContext);
123 | }
124 |
125 | private void inject(Field field, Object inObject, Object toInject) {
126 | ReflectionUtils.setField(
127 | field,
128 | inObject,
129 | toInject
130 | );
131 | }
132 |
133 | /*
134 | * Iterate over all the definitions and put back the original values in the beans
135 | */
136 | @Override
137 | public void afterTestClass(TestContext testContext) throws Exception {
138 | if (isNestedTestClass(testContext.getTestClass())) {
139 | return;
140 | }
141 | ((LinkedList) testContext.getAttribute(ORIGINAL_VALUES_ATTRIBUTE_NAME))
142 | .stream()
143 | .filter(BeanFieldState.class::isInstance)
144 | .map(BeanFieldState.class::cast)
145 | .forEach(fieldState -> fieldState.rollback(testContext));
146 | ROOT_TEST_CONTEXT_TRACKER.remove(testContext.getTestClass());
147 | super.afterTestClass(testContext);
148 | }
149 |
150 | private Class> extractClass(Definition definition) {
151 | Type type = definition.getResolvableType().getType();
152 | if (type instanceof ParameterizedType) {
153 | type = ((ParameterizedType) type).getRawType();
154 | }
155 | return (Class>) type;
156 | }
157 |
158 | private Class> resolveTestClass(Class> candidate) {
159 | if (isNestedTestClass(candidate)) {
160 | return resolveTestClass(candidate.getEnclosingClass());
161 | }
162 | return candidate;
163 | }
164 |
165 | private boolean isNestedTestClass(Class> candidate) {
166 | return AnnotationUtils.isAnnotationDeclaredLocally(Nested.class, candidate)
167 | && candidate.getEnclosingClass() != null;
168 | }
169 |
170 | }
171 |
--------------------------------------------------------------------------------
/src/main/java/com/teketik/test/mockinbean/MockInBeans.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * Container annotation that aggregates several {@link MockInBean @MockInBean} annotations.
10 | *
11 | * Can be used natively, declaring several nested {@link MockInBean @MockInBean} annotations.
12 | * Can also be used in conjunction with Java 8's support for repeatable
13 | * annotations , where {@link MockInBean @MockInBean} can simply be declared several times
14 | * on the same {@linkplain ElementType#TYPE type}, implicitly generating this container
15 | * annotation.
16 | *
17 | * @author Antoine Meyer
18 | */
19 | @Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
20 | @Retention(RetentionPolicy.RUNTIME)
21 | public @interface MockInBeans {
22 |
23 | /**
24 | * @return the contained {@link MockInBean @MockInBean} annotations.
25 | */
26 | MockInBean[] value();
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/teketik/test/mockinbean/ProxiedBeanFieldState.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean;
2 |
3 | import org.springframework.aop.TargetSource;
4 | import org.springframework.test.context.TestContext;
5 | import org.springframework.test.util.ReflectionTestUtils;
6 |
7 | import java.lang.reflect.Field;
8 |
9 | /**
10 | * Special kind of {@link BeanFieldState} handling proxied beans (like aspects).
11 | * The mock is not injected into the field
but into the target
of its {@link TargetSource}.
12 | * @author Antoine Meyer
13 | * @see https://github.com/antoinemeyer/mock-in-bean/issues/23
14 | */
15 | class ProxiedBeanFieldState extends BeanFieldState {
16 |
17 | private static void setTargetSourceValue(TargetSource targetSource, Object value) {
18 | ReflectionTestUtils.setField(targetSource, "target", value);
19 | }
20 |
21 | final TargetSource proxyTargetSource;
22 |
23 | final Object proxyTargetOriginalValue;
24 |
25 | public ProxiedBeanFieldState(Object inBean, Field beanField, Object beanFieldValue, TargetSource proxyTargetSource, Definition definition) throws Exception {
26 | super(inBean, beanField, beanFieldValue, definition);
27 | this.proxyTargetSource = proxyTargetSource;
28 | this.proxyTargetOriginalValue = proxyTargetSource.getTarget();
29 | }
30 |
31 | @Override
32 | public void rollback(TestContext testContext) {
33 | setTargetSourceValue(proxyTargetSource, proxyTargetOriginalValue);
34 | }
35 |
36 | @Override
37 | public Object createMockOrSpy() {
38 | Object applicableMockOrSpy = definition.create(proxyTargetOriginalValue);
39 | setTargetSourceValue(proxyTargetSource, applicableMockOrSpy);
40 | return originalValue; //the 'mock or spy' to operate for proxied beans are the actual proxy
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/teketik/test/mockinbean/SpyDefinition.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean;
2 |
3 | import org.mockito.MockSettings;
4 | import org.mockito.Mockito;
5 | import org.springframework.boot.test.mock.mockito.MockReset;
6 | import org.springframework.core.ResolvableType;
7 | import org.springframework.util.Assert;
8 |
9 | class SpyDefinition extends Definition {
10 |
11 | SpyDefinition(String name, ResolvableType type) {
12 | super(name, type);
13 | }
14 |
15 | @Override
16 | T create(Object originalValue) {
17 | Assert.notNull(originalValue, "originalValue must not be null");
18 | Assert.isInstanceOf(this.resolvableType.resolve(), originalValue);
19 | Assert.state(!Mockito.mockingDetails(originalValue).isSpy(), "originalValue is already a spy");
20 | MockSettings settings = MockReset.withSettings(MockReset.AFTER);
21 | settings.name(name);
22 | settings.spiedInstance(originalValue);
23 | settings.defaultAnswer(Mockito.CALLS_REAL_METHODS);
24 | return (T) Mockito.mock(originalValue.getClass(), settings);
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/teketik/test/mockinbean/SpyInBean.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean;
2 |
3 | import org.mockito.Spy;
4 | import org.springframework.boot.test.mock.mockito.SpyBean;
5 |
6 | import java.lang.annotation.ElementType;
7 | import java.lang.annotation.Repeatable;
8 | import java.lang.annotation.Retention;
9 | import java.lang.annotation.RetentionPolicy;
10 | import java.lang.annotation.Target;
11 |
12 | /**
13 | * Annotation used to inject a {@link Spy} of a Spring Bean in another Spring Bean for the duration of a test.
14 | *
This is a convenient alternative to {@link SpyBean @SpyBean} that provides surgical spy injection without dirtying or polluting the Spring context:
15 | * Spys are injected for every test method and the original Spring Beans(s) are re-injected after the test class is done.
16 | *
17 | * Example:
18 | * Assuming you have the following service:
19 | *
20 | * @Service
21 | * public class MyService {
22 | *
23 | * @Autowired
24 | * private ThirdPartyApi thirdPartyApi;
25 | *
26 | * public void doSomethingWithThirdPartyApi() {
27 | * thirdPartyApi.doSomething(new Object());
28 | * }
29 | * }
30 | *
31 | * You can create a test for your service with a spied {@code ThirdPartyApi} like:
32 | *
33 | * @SpringBootTest
34 | * public class MyServiceTest {
35 | *
36 | * @SpyInBean(MyService.class)
37 | * private ThirdPartyApi thirdPartyApi;
38 | *
39 | * @Autowired
40 | * private MyService myService;
41 | *
42 | * @Test
43 | * public void test() {
44 | * myService.doSomethingWithThirdPartyApi();
45 | * Mockito.verify(thirdPartyApi).doSomething(Mockito.any(Object.class));
46 | * }
47 | * }
48 | *
49 | * {@code thirdPartyApi} will be a {@link Spy} of the actual {@code ThirdPartyApi} Spring Bean that is recreated for every test method in {@code MyServiceTest} and {@code MyService}.
50 | * The original {@code ThirdPartyApi} Spring bean will be re-injected in {@code MyService} after all the tests of {@code MyServiceTest}.
51 | *
52 | * In case the bean in which you are trying to inject a spy has multiple instances registered in the context, you can specify the {@link #name() name} of the bean:
53 | *
54 | * @SpyInBean(value = MyService.class, name = "nameOfMyService")
55 | * private ThirdPartyApi thirdPartyApi;
56 | *
57 | *
58 | * You can also inject your spy in multiple beans by repeating the annotation:
59 | *
60 | * @SpyInBean(MyFirstService.class),
61 | * @SpyInBean(MySecondService.class)
62 | * private ThirdPartyApi thirdPartyApi;
63 | *
64 | * @author Antoine Meyer
65 | * @see MockInBean
66 | */
67 | @Target({ElementType.FIELD})
68 | @Retention(RetentionPolicy.RUNTIME)
69 | @Repeatable(SpyInBeans.class)
70 | public @interface SpyInBean {
71 |
72 | /**
73 | * @return the {@code class} of the Spring Bean in which you want your {@link Spy} to be injected for the duration of the test.
74 | */
75 | Class> value();
76 |
77 | /**
78 | * @return the {@code name} of the Spring Bean in which you want your {@link Spy} to be injected for the duration of the test.
79 | * This is not necessary and is only useful if your context contains multiple Beans of the same type with different names.
80 | */
81 | String name() default "";
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/com/teketik/test/mockinbean/SpyInBeans.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * Container annotation that aggregates several {@link SpyInBean @SpyInBean} annotations.
10 | *
11 | * Can be used natively, declaring several nested {@link SpyInBean @SpyInBean} annotations.
12 | * Can also be used in conjunction with Java 8's support for repeatable
13 | * annotations , where {@link SpyInBean @SpyInBean} can simply be declared several times
14 | * on the same {@linkplain ElementType#TYPE type}, implicitly generating this container
15 | * annotation.
16 | *
17 | * @author Antoine Meyer
18 | */
19 | @Target({ElementType.FIELD})
20 | @Retention(RetentionPolicy.RUNTIME)
21 | public @interface SpyInBeans {
22 |
23 | /**
24 | * @return the contained {@link SpyInBean} annotations.
25 | */
26 | SpyInBean[] value();
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/teketik/test/mockinbean/TestFieldState.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean;
2 |
3 | import org.springframework.test.context.TestContext;
4 |
5 | import java.lang.reflect.Field;
6 |
7 | class TestFieldState extends FieldState {
8 |
9 | TestFieldState(Field targetField, Definition definition) {
10 | super(targetField, definition);
11 | }
12 |
13 | @Override
14 | Object resolveTarget(TestContext testContext) {
15 | return testContext.getTestInstance();
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/spring.factories:
--------------------------------------------------------------------------------
1 | org.springframework.test.context.TestExecutionListener = \
2 | com.teketik.test.mockinbean.MockInBeanTestExecutionListener
3 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/BeanUtils_FindBeanTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean;
2 |
3 | import org.junit.jupiter.api.Assertions;
4 | import org.junit.jupiter.api.Test;
5 | import org.springframework.boot.test.context.SpringBootTest;
6 | import org.springframework.context.ApplicationContext;
7 | import org.springframework.context.annotation.Bean;
8 | import org.springframework.context.annotation.Configuration;
9 |
10 | import javax.annotation.Resource;
11 |
12 | @SpringBootTest(classes = BeanUtils_FindBeanTest.FindBeanTestConfig.class)
13 | class BeanUtils_FindBeanTest {
14 |
15 | @Configuration
16 | static class FindBeanTestConfig {
17 |
18 | static final String STRING = "a";
19 | static final int INT_1 = 1;
20 | static final int INT_2 = 2;
21 |
22 | @Bean
23 | String string() {
24 | return STRING;
25 | }
26 |
27 | @Bean
28 | Integer int1() {
29 | return INT_1;
30 | }
31 |
32 | @Bean
33 | Integer int2() {
34 | return INT_2;
35 | }
36 |
37 | }
38 |
39 | @Resource
40 | private ApplicationContext applicationContext;
41 |
42 | @Test
43 | public void testFindBean_notFound() {
44 | try {
45 | BeanUtils.findBean(Long.class, "does not matter", applicationContext);
46 | Assertions.fail();
47 | } catch (IllegalArgumentException e) {
48 | Assertions.assertEquals("No beans of type class java.lang.Long", e.getMessage());
49 | }
50 | }
51 |
52 | @Test
53 | public void testFindBean_oneTypeMatch() {
54 | Assertions.assertEquals(FindBeanTestConfig.STRING, BeanUtils.findBean(String.class, "different name", applicationContext));
55 | }
56 |
57 | @Test
58 | public void testFindBean_found() {
59 | Assertions.assertEquals(FindBeanTestConfig.STRING, BeanUtils.findBean(String.class, "object", applicationContext));
60 | }
61 |
62 | @Test
63 | public void testFindBean_invalidType() {
64 | // bean name does not matter, we resolve per type first
65 | Assertions.assertEquals(FindBeanTestConfig.STRING, BeanUtils.findBean(String.class, "object", applicationContext));
66 | }
67 |
68 | @Test
69 | public void testFindBean_noName() {
70 | try {
71 | BeanUtils.findBean(Integer.class, null, applicationContext);
72 | Assertions.fail();
73 | } catch (IllegalArgumentException e) {
74 | Assertions.assertEquals("Multiple beans of type class java.lang.Integer. A name must be provided", e.getMessage());
75 | }
76 | }
77 |
78 | @Test
79 | public void testFindBean_multiTypeAndName() {
80 | Assertions.assertEquals(FindBeanTestConfig.INT_1, BeanUtils.findBean(Integer.class, "int1", applicationContext));
81 | Assertions.assertEquals(FindBeanTestConfig.INT_2, BeanUtils.findBean(Integer.class, "int2", applicationContext));
82 | }
83 |
84 | @Test
85 | public void testFindBean_multiTypeAndNameNotFound() {
86 | try {
87 | BeanUtils.findBean(Integer.class, "int3", applicationContext);
88 | Assertions.fail();
89 | } catch (IllegalArgumentException e) {
90 | Assertions.assertEquals("No beans of type class java.lang.Integer and name int3", e.getMessage());
91 | }
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/BeanUtils_findFieldTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean;
2 |
3 | import org.junit.jupiter.api.Assertions;
4 | import org.junit.jupiter.api.Test;
5 |
6 | class BeanUtils_findFieldTest {
7 |
8 | abstract class BaseClazz {
9 | String baseField;
10 | }
11 |
12 | class Clazz1 extends BaseClazz {
13 | String field1;
14 | String baseField;
15 | }
16 |
17 | @Test
18 | public void testFindField_oneMatch() {
19 | Assertions.assertNull(BeanUtils.findField(BaseClazz.class, null, Integer.class));
20 | Assertions.assertEquals("baseField", BeanUtils.findField(BaseClazz.class, null, String.class).getName());
21 | Assertions.assertEquals("baseField", BeanUtils.findField(BaseClazz.class, "baseField", String.class).getName());
22 | Assertions.assertEquals("baseField", BeanUtils.findField(BaseClazz.class, "not used", String.class).getName());
23 | }
24 |
25 | @Test
26 | public void testFindField_multiMatch() {
27 | try {
28 | BeanUtils.findField(Clazz1.class, null, String.class).getName();
29 | Assertions.fail();
30 | } catch (IllegalArgumentException e) {
31 | Assertions.assertEquals("Multiple fields of type class java.lang.String in class com.teketik.test.mockinbean.BeanUtils_findFieldTest$Clazz1. Please specify a name.", e.getMessage());
32 | }
33 | try {
34 | BeanUtils.findField(Clazz1.class, "baseField", String.class).getName();
35 | Assertions.fail();
36 | } catch (IllegalArgumentException e) {
37 | Assertions.assertEquals("Multiple fields of type class java.lang.String in class com.teketik.test.mockinbean.BeanUtils_findFieldTest$Clazz1 with name baseField", e.getMessage());
38 | }
39 | try {
40 | BeanUtils.findField(Clazz1.class, "no match", String.class).getName();
41 | Assertions.fail();
42 | } catch (IllegalArgumentException e) {
43 | Assertions.assertEquals("Multiple fields of type class java.lang.String in class com.teketik.test.mockinbean.BeanUtils_findFieldTest$Clazz1 and none with name no match", e.getMessage());
44 | }
45 | Assertions.assertEquals("field1", BeanUtils.findField(Clazz1.class, "field1", String.class).getName());
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/MockInBeanTestExecutionListenerBridge.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean;
2 |
3 | /**
4 | * Public bridge of {@link MockInBeanTestExecutionListener}.
5 | * @author Antoine Meyer
6 | */
7 | public abstract class MockInBeanTestExecutionListenerBridge extends MockInBeanTestExecutionListener {
8 | }
9 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/data/Customer.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.data;
2 |
3 | import javax.persistence.Entity;
4 | import javax.persistence.GeneratedValue;
5 | import javax.persistence.GenerationType;
6 | import javax.persistence.Id;
7 |
8 | @Entity
9 | public class Customer {
10 |
11 | @Id
12 | @GeneratedValue(strategy=GenerationType.AUTO)
13 | private Long id;
14 | private String firstName;
15 | private String lastName;
16 |
17 | protected Customer() {}
18 |
19 | public Customer(String firstName, String lastName) {
20 | this.firstName = firstName;
21 | this.lastName = lastName;
22 | }
23 |
24 | public Long getId() {
25 | return id;
26 | }
27 |
28 | public String getFirstName() {
29 | return firstName;
30 | }
31 |
32 | public String getLastName() {
33 | return lastName;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/data/CustomerRepository.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.data;
2 |
3 | import java.util.List;
4 |
5 | import org.springframework.data.repository.CrudRepository;
6 |
7 | public interface CustomerRepository extends CrudRepository {
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/data/CustomerService.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.data;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.stereotype.Service;
5 |
6 |
7 | @Service
8 | public class CustomerService {
9 |
10 | @Autowired
11 | private CustomerRepository customerRepository;
12 |
13 | public Customer save(Customer customer) {
14 | return customerRepository.save(customer);
15 | }
16 |
17 | }
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/data/CustomerServiceMockTest.java:
--------------------------------------------------------------------------------
1 |
2 | package com.teketik.test.mockinbean.data;
3 |
4 | import com.teketik.test.mockinbean.MockInBean;
5 |
6 | import org.junit.jupiter.api.Assertions;
7 | import org.junit.jupiter.api.Test;
8 | import org.mockito.Mockito;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.boot.test.context.SpringBootTest;
11 | import org.springframework.test.context.ContextConfiguration;
12 |
13 | @SpringBootTest
14 | @ContextConfiguration
15 | public class CustomerServiceMockTest {
16 |
17 | @Autowired
18 | private CustomerService customerService;
19 |
20 | @MockInBean(value = CustomerService.class)
21 | private CustomerRepository customerRepository;
22 |
23 | private final Customer customer = new Customer("first", "last");
24 |
25 | @Test
26 | public void test() {
27 | Customer mockReturn = new Customer();
28 | Mockito.when(customerRepository.save(customer)).thenReturn(mockReturn);
29 |
30 | Customer serviceReturn = customerService.save(customer);
31 | Assertions.assertSame(serviceReturn, mockReturn);
32 | }
33 |
34 | @Test
35 | public void test2() {
36 | Customer mockReturn = new Customer();
37 | Mockito.when(customerRepository.save(customer)).thenReturn(mockReturn);
38 |
39 | Customer serviceReturn = customerService.save(customer);
40 | Assertions.assertSame(serviceReturn, mockReturn);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/data/DataApplication.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.data;
2 |
3 | import org.springframework.boot.autoconfigure.SpringBootApplication;
4 |
5 | @SpringBootApplication
6 | public class DataApplication {
7 | }
8 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/AssertingCleanTestExecutionListener.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import org.junit.jupiter.api.Assertions;
4 | import org.springframework.context.ApplicationContext;
5 | import org.springframework.core.Ordered;
6 | import org.springframework.test.context.TestContext;
7 | import org.springframework.test.context.TestExecutionListener;
8 | import org.springframework.util.ReflectionUtils;
9 |
10 | import java.util.concurrent.atomic.AtomicInteger;
11 |
12 | /**
13 | * Ensures that the context is clean after the test (by ensuring none of the spring beans contain a mock or a spy).
14 | * @author Antoine Meyer
15 | */
16 | class AssertingCleanTestExecutionListener implements TestExecutionListener, Ordered {
17 |
18 | @Override
19 | public void afterTestClass(TestContext testContext) throws Exception {
20 | final AtomicInteger verifiedCounter = new AtomicInteger();
21 | final ApplicationContext applicationContext = testContext.getApplicationContext();
22 | for (String beanName : applicationContext.getBeanDefinitionNames()) {
23 | final Object bean = applicationContext.getBean(beanName);
24 | if (bean.getClass().getName().startsWith("com.teketik.test.mockinbean.test.components")) {
25 | ReflectionUtils.doWithFields(bean.getClass(), field -> {
26 | field.setAccessible(true);
27 | Assertions.assertFalse(TestUtils.isMockOrSpy(field.get(bean)));
28 | verifiedCounter.getAndIncrement();
29 | });
30 | }
31 | }
32 | Assertions.assertTrue(verifiedCounter.get() >= 10);
33 | }
34 |
35 | @Override
36 | public int getOrder() {
37 | return Integer.MAX_VALUE;
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/BaseTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import org.springframework.boot.test.context.SpringBootTest;
4 | import org.springframework.test.context.TestExecutionListeners;
5 | import org.springframework.test.context.TestExecutionListeners.MergeMode;
6 |
7 | @TestExecutionListeners(
8 | value = { AssertingCleanTestExecutionListener.class },
9 | mergeMode = MergeMode.MERGE_WITH_DEFAULTS
10 | )
11 | @SpringBootTest
12 | abstract class BaseTest {
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/ComponentWithDifferentNameTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import com.teketik.test.mockinbean.MockInBean;
4 | import com.teketik.test.mockinbean.test.components.MockableComponent1;
5 | import com.teketik.test.mockinbean.test.components.TestComponent1;
6 |
7 | import org.junit.jupiter.api.Assertions;
8 | import org.junit.jupiter.api.Test;
9 | import org.springframework.test.util.ReflectionTestUtils;
10 |
11 | import javax.annotation.Resource;
12 |
13 | class ComponentWithDifferentNameTest extends BaseTest {
14 |
15 | @MockInBean(TestComponent1.class)
16 | private MockableComponent1 differentName;
17 |
18 | @Resource
19 | private TestComponent1 testComponent1;
20 |
21 | @Test
22 | public void test() {
23 | Assertions.assertTrue(TestUtils.isMock(differentName));
24 | Assertions.assertSame(differentName, ReflectionTestUtils.getField(testComponent1, "mockableComponent1"));
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/GenericComponentTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import com.teketik.test.mockinbean.MockInBean;
4 | import com.teketik.test.mockinbean.test.components.GenericMockableComponent;
5 | import com.teketik.test.mockinbean.test.components.GenericTestComponent;
6 |
7 | import org.junit.jupiter.api.Assertions;
8 | import org.junit.jupiter.api.Test;
9 | import org.springframework.test.util.ReflectionTestUtils;
10 |
11 | import javax.annotation.Resource;
12 |
13 | class GenericComponentTest extends BaseTest {
14 |
15 | @MockInBean(GenericTestComponent.class)
16 | private GenericMockableComponent genericMockableComponent;
17 |
18 | @Resource
19 | private GenericTestComponent genericTestComponent;
20 |
21 | @Test
22 | public void test() {
23 | Assertions.assertTrue(TestUtils.isMock(genericMockableComponent));
24 | Assertions.assertSame(genericMockableComponent, ReflectionTestUtils.getField(genericTestComponent, "genericMockableComponent"));
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/InterfaceImplementation1Test.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 |
4 | import com.teketik.test.mockinbean.MockInBean;
5 | import com.teketik.test.mockinbean.test.InterfaceImplementationTestConfig.LoggingService;
6 | import com.teketik.test.mockinbean.test.InterfaceImplementationTestConfig.ProviderServiceImpl;
7 |
8 | import org.junit.jupiter.api.Assertions;
9 | import org.junit.jupiter.api.Test;
10 | import org.mockito.Mockito;
11 | import org.springframework.beans.factory.annotation.Autowired;
12 | import org.springframework.context.annotation.Import;
13 |
14 | @Import(InterfaceImplementationTestConfig.class)
15 | public class InterfaceImplementation1Test extends BaseTest {
16 |
17 | @Autowired
18 | protected LoggingService loggingService;
19 |
20 | @MockInBean(LoggingService.class)
21 | private ProviderServiceImpl providerService;
22 |
23 | @MockInBean(LoggingService.class)
24 | private ProviderServiceImpl providerServiceImpl;
25 |
26 | @Test
27 | public void test() {
28 | Mockito.when(providerService.provideValue()).thenReturn("mocked value");
29 | Mockito.when(providerServiceImpl.provideValue()).thenReturn("mocked value 2");
30 |
31 | Assertions.assertEquals("mocked value", loggingService.logCurrentValue());
32 | Assertions.assertEquals("mocked value 2", loggingService.logCurrentValueWithImpl());
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/InterfaceImplementation2Test.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 |
4 | import com.teketik.test.mockinbean.MockInBean;
5 | import com.teketik.test.mockinbean.test.InterfaceImplementationTestConfig.LoggingService;
6 | import com.teketik.test.mockinbean.test.InterfaceImplementationTestConfig.ProviderService;
7 | import com.teketik.test.mockinbean.test.InterfaceImplementationTestConfig.ProviderServiceImpl;
8 |
9 | import org.junit.jupiter.api.Assertions;
10 | import org.junit.jupiter.api.Test;
11 | import org.mockito.Mockito;
12 | import org.springframework.beans.factory.annotation.Autowired;
13 | import org.springframework.context.annotation.Import;
14 |
15 | @Import(InterfaceImplementationTestConfig.class)
16 | public class InterfaceImplementation2Test extends BaseTest {
17 |
18 | @Autowired
19 | protected LoggingService loggingService;
20 |
21 | @MockInBean(LoggingService.class)
22 | private ProviderService providerServiceMock;
23 |
24 | @MockInBean(LoggingService.class)
25 | private ProviderServiceImpl providerServiceImpl;
26 |
27 | @Test
28 | public void test() {
29 | Mockito.when(providerServiceMock.provideValue()).thenReturn("mocked value");
30 | Mockito.when(providerServiceImpl.provideValue()).thenReturn("mocked value 2");
31 |
32 | Assertions.assertEquals("mocked value", loggingService.logCurrentValue());
33 | Assertions.assertEquals("mocked value 2", loggingService.logCurrentValueWithImpl());
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/InterfaceImplementationTestConfig.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.stereotype.Component;
5 | import org.springframework.stereotype.Service;
6 |
7 | class InterfaceImplementationTestConfig {
8 |
9 | public interface ProviderService {
10 | public String provideValue();
11 | }
12 |
13 | @Service
14 | public static class ProviderServiceImpl implements ProviderService {
15 | @Override
16 | public String provideValue() {
17 | return "value1";
18 | }
19 | }
20 |
21 | @Component
22 | public static class LoggingService {
23 |
24 | @Autowired
25 | private ProviderService providerService;
26 |
27 | @Autowired
28 | private ProviderServiceImpl providerServiceImpl;
29 |
30 | public String logCurrentValue() {
31 | return providerService.provideValue();
32 | }
33 |
34 | public String logCurrentValueWithImpl() {
35 | return providerServiceImpl.provideValue();
36 | }
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/InvalidBeanNameResolvedByTypeTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import com.teketik.test.mockinbean.MockInBean;
4 | import com.teketik.test.mockinbean.test.components.MockableComponent1;
5 | import com.teketik.test.mockinbean.test.components.TestComponent1;
6 |
7 | import org.junit.jupiter.api.Assertions;
8 | import org.junit.jupiter.api.Test;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.test.util.ReflectionTestUtils;
11 |
12 | class InvalidBeanNameResolvedByTypeTest extends BaseTest {
13 |
14 | @MockInBean(value = TestComponent1.class, name = "not a valid name")
15 | private MockableComponent1 mockableComponent1;
16 |
17 | @Autowired
18 | protected TestComponent1 testComponent1;
19 |
20 | @Test
21 | public void test() {
22 | Assertions.assertTrue(TestUtils.isMock(mockableComponent1));
23 | Assertions.assertSame(mockableComponent1, ReflectionTestUtils.getField(testComponent1, "mockableComponent1"));
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/InvalidBeanNameUnResolvedTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import com.teketik.test.mockinbean.MockInBean;
4 | import com.teketik.test.mockinbean.MockInBeanTestExecutionListenerBridge;
5 | import com.teketik.test.mockinbean.test.components.MockableComponent1;
6 | import com.teketik.test.mockinbean.test.components.TestComponent1;
7 | import com.teketik.test.mockinbean.test.components.TestComponent3;
8 |
9 | import org.junit.jupiter.api.Assertions;
10 | import org.junit.jupiter.api.Test;
11 | import org.springframework.beans.factory.annotation.Autowired;
12 | import org.springframework.test.context.TestContext;
13 | import org.springframework.test.context.TestExecutionListeners;
14 |
15 | @TestExecutionListeners(
16 | value = { InvalidBeanNameUnResolvedTest.TestExecutionListener.class },
17 | inheritListeners = false
18 | )
19 | class InvalidBeanNameUnResolvedTest extends BaseTest {
20 |
21 | static class TestExecutionListener extends MockInBeanTestExecutionListenerBridge {
22 |
23 | @Override
24 | public void beforeTestClass(TestContext testContext) throws Exception {
25 | try {
26 | super.beforeTestClass(testContext);
27 | Assertions.fail();
28 | } catch (IllegalArgumentException e) {
29 | Assertions.assertEquals(
30 | "No beans of type class com.teketik.test.mockinbean.test.components.TestComponent3 and name invalid name",
31 | e.getMessage()
32 | );
33 | }
34 | }
35 |
36 | @Override
37 | public void beforeTestMethod(TestContext testContext) throws Exception {
38 | // empty
39 | }
40 |
41 | @Override
42 | public void afterTestClass(TestContext testContext) throws Exception {
43 | // empty
44 | }
45 |
46 | }
47 |
48 | @MockInBean(value = TestComponent3.class, name = "invalid name")
49 | private MockableComponent1 mockableComponent1;
50 |
51 | @Autowired
52 | private TestComponent1 testComponent1;
53 |
54 | @Test
55 | public void test() {
56 | Assertions.assertNull(mockableComponent1);
57 | Assertions.assertNull(testComponent1);
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/MockInBeanBaseTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import com.teketik.test.mockinbean.test.components.MockableComponent1;
4 | import com.teketik.test.mockinbean.test.components.MockableComponent2;
5 | import com.teketik.test.mockinbean.test.components.TestComponent1;
6 | import com.teketik.test.mockinbean.test.components.TestComponent2;
7 |
8 | import org.junit.jupiter.api.Assertions;
9 | import org.junit.jupiter.api.Test;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.test.context.TestContext;
12 | import org.springframework.test.context.TestExecutionListeners;
13 | import org.springframework.test.context.support.AbstractTestExecutionListener;
14 |
15 |
16 | @TestExecutionListeners(
17 | value = { MockInBeanBaseTest.TestExecutionListener.class }
18 | )
19 | abstract class MockInBeanBaseTest extends BaseTest {
20 |
21 | static class TestExecutionListener extends AbstractTestExecutionListener {
22 |
23 | private static MockableComponent1 mockableComponent1firstTest;
24 | private static MockableComponent2 mockableComponent2firstTest;
25 | private static boolean multiTestChecked;
26 |
27 | @Override
28 | public void afterTestMethod(TestContext testContext) throws Exception {
29 | final MockInBeanBaseTest baseTest = (MockInBeanBaseTest) testContext.getTestInstance();
30 | if (mockableComponent1firstTest == null && mockableComponent2firstTest == null) {
31 | mockableComponent1firstTest = baseTest.getMockableComponent1();
32 | mockableComponent2firstTest = baseTest.getMockableComponent2();
33 | } else {
34 | Assertions.assertNotSame(baseTest.getMockableComponent1(), mockableComponent1firstTest);
35 | Assertions.assertNotSame(baseTest.getMockableComponent2(), mockableComponent2firstTest);
36 | multiTestChecked = true;
37 | }
38 | }
39 |
40 | @Override
41 | public void afterTestClass(TestContext testContext) throws Exception {
42 | Assertions.assertTrue(multiTestChecked);
43 | }
44 | }
45 |
46 | @Autowired
47 | protected TestComponent1 testComponent1;
48 |
49 | @Autowired
50 | protected TestComponent2 testComponent2;
51 |
52 | @Test
53 | public void emptyTestForMockRecreationVerification() {
54 | }
55 |
56 | abstract MockableComponent1 getMockableComponent1();
57 |
58 | abstract MockableComponent2 getMockableComponent2();
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/MockInBeanMetaAnnotationsTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import com.teketik.test.mockinbean.test.annotations.MockInBeanWithCustomName;
4 | import com.teketik.test.mockinbean.test.annotations.MockInMultipleComponents;
5 | import com.teketik.test.mockinbean.test.components.MockableComponent1;
6 | import com.teketik.test.mockinbean.test.components.MockableComponent2;
7 | import org.junit.jupiter.api.Assertions;
8 | import org.junit.jupiter.api.Test;
9 | import org.springframework.test.util.ReflectionTestUtils;
10 |
11 | class MockInBeanMetaAnnotationsTest extends MockInBeanBaseTest {
12 |
13 | @MockInMultipleComponents
14 | private MockableComponent1 mockableComponent1;
15 |
16 | @MockInBeanWithCustomName
17 | private MockableComponent2 mockableComponent2;
18 |
19 | /**
20 | * Note: Will fail if run individually, run as a class.
21 | */
22 | @Test
23 | void test() {
24 | Assertions.assertTrue(TestUtils.isMock(mockableComponent1));
25 | Assertions.assertSame(mockableComponent1, ReflectionTestUtils.getField(testComponent1, "mockableComponent1"));
26 | Assertions.assertSame(mockableComponent1, ReflectionTestUtils.getField(testComponent2, "mockableComponent1"));
27 |
28 | Assertions.assertTrue(TestUtils.isMock(mockableComponent2));
29 | Assertions.assertSame(mockableComponent2, ReflectionTestUtils.getField(testComponent1, "mockableComponent2"));
30 | Assertions.assertNotSame(mockableComponent2, ReflectionTestUtils.getField(testComponent2, "mockableComponent2"));
31 | Assertions.assertFalse(TestUtils.isMockOrSpy(ReflectionTestUtils.getField(testComponent2, "mockableComponent2")));
32 | }
33 |
34 | @Override
35 | MockableComponent1 getMockableComponent1() {
36 | return mockableComponent1;
37 | }
38 |
39 | @Override
40 | MockableComponent2 getMockableComponent2() {
41 | return mockableComponent2;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/MockInBeanTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import com.teketik.test.mockinbean.MockInBean;
4 | import com.teketik.test.mockinbean.MockInBeans;
5 | import com.teketik.test.mockinbean.test.components.MockableComponent1;
6 | import com.teketik.test.mockinbean.test.components.MockableComponent2;
7 | import com.teketik.test.mockinbean.test.components.TestComponent1;
8 | import com.teketik.test.mockinbean.test.components.TestComponent2;
9 |
10 | import org.junit.jupiter.api.Assertions;
11 | import org.junit.jupiter.api.Test;
12 | import org.springframework.test.util.ReflectionTestUtils;
13 |
14 | class MockInBeanTest extends MockInBeanBaseTest {
15 |
16 | @MockInBeans({
17 | @MockInBean(TestComponent1.class),
18 | @MockInBean(TestComponent2.class)
19 | })
20 | private MockableComponent1 mockableComponent1;
21 |
22 | @MockInBean(TestComponent1.class)
23 | private MockableComponent2 mockableComponent2;
24 |
25 | /**
26 | * Note: Will fail if run individually, run as a class.
27 | */
28 | @Test
29 | public void test() {
30 | Assertions.assertTrue(TestUtils.isMock(mockableComponent1));
31 | Assertions.assertSame(mockableComponent1, ReflectionTestUtils.getField(testComponent1, "mockableComponent1"));
32 | Assertions.assertSame(mockableComponent1, ReflectionTestUtils.getField(testComponent2, "mockableComponent1"));
33 |
34 | Assertions.assertTrue(TestUtils.isMock(mockableComponent2));
35 | Assertions.assertSame(mockableComponent2, ReflectionTestUtils.getField(testComponent1, "mockableComponent2"));
36 | Assertions.assertNotSame(mockableComponent2, ReflectionTestUtils.getField(testComponent2, "mockableComponent2"));
37 | Assertions.assertFalse(TestUtils.isMockOrSpy(ReflectionTestUtils.getField(testComponent2, "mockableComponent2")));
38 | }
39 |
40 | @Override
41 | MockableComponent1 getMockableComponent1() {
42 | return mockableComponent1;
43 | }
44 |
45 | @Override
46 | MockableComponent2 getMockableComponent2() {
47 | return mockableComponent2;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/MockInSpyTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import com.teketik.test.mockinbean.MockInBean;
4 | import com.teketik.test.mockinbean.SpyInBean;
5 | import com.teketik.test.mockinbean.test.MockInSpyTest.Config.TestComponent1Wrapper;
6 | import com.teketik.test.mockinbean.test.components.MockableComponent1;
7 | import com.teketik.test.mockinbean.test.components.TestComponent1;
8 |
9 | import org.junit.jupiter.api.Assertions;
10 | import org.junit.jupiter.api.Test;
11 | import org.mockito.Mockito;
12 | import org.springframework.stereotype.Component;
13 |
14 | import javax.annotation.Resource;
15 |
16 | class MockInSpyTest extends BaseTest {
17 |
18 | @org.springframework.boot.test.context.TestConfiguration
19 | static class Config {
20 |
21 | @Component
22 | class TestComponent1Wrapper {
23 |
24 | @Resource
25 | private TestComponent1 testComponent1;
26 |
27 | void doWith1() {
28 | testComponent1.doWith1();
29 | }
30 |
31 | void doWith2() {
32 | testComponent1.doWith2();
33 | }
34 |
35 | }
36 | }
37 |
38 | @Resource
39 | private TestComponent1Wrapper testComponent1Wrapper;
40 |
41 | @SpyInBean(TestComponent1Wrapper.class)
42 | private TestComponent1 testComponent1;
43 |
44 | @MockInBean(TestComponent1.class)
45 | private MockableComponent1 mockableComponent1;
46 |
47 | @Test
48 | public void testMocked1() {
49 | Mockito.doNothing().when(mockableComponent1).doSomething();
50 | testComponent1Wrapper.doWith1();
51 | Mockito.verify(mockableComponent1).doSomething();
52 | }
53 |
54 | @Test
55 | public void testMocked2() {
56 | final RuntimeException runtimeException = new RuntimeException();
57 | Mockito.doThrow(runtimeException).when(mockableComponent1).doSomething();
58 | try {
59 | testComponent1Wrapper.doWith1();
60 | Assertions.fail();
61 | } catch (Exception e) {
62 | Assertions.assertSame(runtimeException, e);
63 | }
64 | }
65 |
66 | @Test
67 | public void testNotMocked() {
68 | try {
69 | testComponent1Wrapper.doWith2();
70 | Assertions.fail();
71 | } catch (Exception e) {
72 | Assertions.assertTrue(e instanceof UnsupportedOperationException);
73 | }
74 | }
75 |
76 | }
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/MultipleComponentsOfSameTypeTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import com.teketik.test.mockinbean.MockInBean;
4 | import com.teketik.test.mockinbean.test.components.MockableComponent1;
5 | import com.teketik.test.mockinbean.test.components.TestComponentWithMultipleSameType;
6 |
7 | import org.junit.jupiter.api.Assertions;
8 | import org.junit.jupiter.api.Test;
9 | import org.springframework.test.util.ReflectionTestUtils;
10 |
11 | import javax.annotation.Resource;
12 |
13 | class MultipleComponentsOfSameTypeTest extends BaseTest {
14 |
15 | @MockInBean(TestComponentWithMultipleSameType.class)
16 | private MockableComponent1 mockableComponent1a;
17 |
18 | @MockInBean(TestComponentWithMultipleSameType.class)
19 | private MockableComponent1 mockableComponent1b;
20 |
21 | @Resource
22 | private TestComponentWithMultipleSameType testComponentWithMultipleSameTypeDependencies;
23 |
24 | @Test
25 | public void test() {
26 | Assertions.assertTrue(TestUtils.isMock(mockableComponent1a));
27 | Assertions.assertTrue(TestUtils.isMock(mockableComponent1b));
28 | Assertions.assertSame(mockableComponent1a, ReflectionTestUtils.getField(testComponentWithMultipleSameTypeDependencies, "mockableComponent1a"));
29 | Assertions.assertSame(mockableComponent1b, ReflectionTestUtils.getField(testComponentWithMultipleSameTypeDependencies, "mockableComponent1b"));
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/MultipleComponentsThroughInheritanceTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import com.teketik.test.mockinbean.MockInBean;
4 | import com.teketik.test.mockinbean.MockInBeans;
5 | import com.teketik.test.mockinbean.test.components.MockableComponent1;
6 | import com.teketik.test.mockinbean.test.components.TestComponent3;
7 | import com.teketik.test.mockinbean.test.components.TestComponentOn3;
8 |
9 | import org.junit.jupiter.api.Assertions;
10 | import org.junit.jupiter.api.Test;
11 | import org.springframework.test.util.ReflectionTestUtils;
12 |
13 | import javax.annotation.Resource;
14 |
15 | class MultipleComponentsThroughInheritanceTest extends BaseTest {
16 |
17 | @MockInBeans({
18 | @MockInBean(TestComponentOn3.class),
19 | @MockInBean(value = TestComponent3.class, name = "testComponent3"),
20 | })
21 | private MockableComponent1 mockableComponent1;
22 |
23 | @Resource
24 | private TestComponentOn3 testComponentOn3;
25 |
26 | @Resource
27 | private TestComponent3 testComponent3;
28 |
29 | @Test
30 | public void test() {
31 | Assertions.assertTrue(TestUtils.isMock(mockableComponent1));
32 | Assertions.assertSame(mockableComponent1, ReflectionTestUtils.getField(testComponentOn3, "mockableComponent1"));
33 | Assertions.assertSame(mockableComponent1, ReflectionTestUtils.getField(testComponent3, "mockableComponent1"));
34 | Assertions.assertFalse(TestUtils.isMockOrSpy(ReflectionTestUtils.getField(testComponentOn3, "mockableComponent2")));
35 | Assertions.assertFalse(TestUtils.isMockOrSpy(ReflectionTestUtils.getField(testComponent3, "mockableComponent2")));
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/MultipleMockDefinitionForAFieldTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import com.teketik.test.mockinbean.MockInBean;
4 | import com.teketik.test.mockinbean.MockInBeanTestExecutionListenerBridge;
5 | import com.teketik.test.mockinbean.SpyInBean;
6 | import com.teketik.test.mockinbean.test.components.MockableComponent1;
7 | import com.teketik.test.mockinbean.test.components.TestComponent1;
8 |
9 | import org.junit.jupiter.api.Assertions;
10 | import org.junit.jupiter.api.Test;
11 | import org.springframework.test.context.TestContext;
12 | import org.springframework.test.context.TestExecutionListeners;
13 |
14 | import javax.annotation.Resource;
15 |
16 | @TestExecutionListeners(
17 | value = { MultipleMockDefinitionForAFieldTest.TestExecutionListener.class },
18 | inheritListeners = false
19 | )
20 | class MultipleMockDefinitionForAFieldTest extends BaseTest {
21 |
22 | static class TestExecutionListener extends MockInBeanTestExecutionListenerBridge {
23 |
24 | @Override
25 | public void beforeTestClass(TestContext testContext) throws Exception {
26 | try {
27 | super.beforeTestClass(testContext);
28 | Assertions.fail();
29 | } catch (IllegalArgumentException e) {
30 | Assertions.assertEquals(
31 | "private com.teketik.test.mockinbean.test.components.MockableComponent1 com.teketik.test.mockinbean.test.components.TestComponentBase.mockableComponent1 can only be mapped once, as a mock or a spy, not both!",
32 | e.getMessage()
33 | );
34 | }
35 | }
36 |
37 | @Override
38 | public void beforeTestMethod(TestContext testContext) throws Exception {
39 | // empty
40 | }
41 |
42 | @Override
43 | public void afterTestClass(TestContext testContext) throws Exception {
44 | // empty
45 | }
46 |
47 | }
48 |
49 | @MockInBean(TestComponent1.class)
50 | private MockableComponent1 mockableComponent1a;
51 |
52 | @SpyInBean(TestComponent1.class)
53 | private MockableComponent1 mockableComponent1b;
54 |
55 | @Resource
56 | private TestComponent1 testComponent1;
57 |
58 | @Test
59 | public void test() {
60 | Assertions.assertNull(mockableComponent1a);
61 | Assertions.assertNull(mockableComponent1b);
62 | Assertions.assertNull(testComponent1);
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/NestedTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import com.teketik.test.mockinbean.MockInBean;
4 | import com.teketik.test.mockinbean.SpyInBean;
5 | import com.teketik.test.mockinbean.test.example.ExpensiveProcessor;
6 | import com.teketik.test.mockinbean.test.example.MyService;
7 | import com.teketik.test.mockinbean.test.example.ThirdPartyApiService;
8 |
9 | import org.junit.jupiter.api.Assertions;
10 | import org.junit.jupiter.api.Nested;
11 | import org.junit.jupiter.api.Test;
12 | import org.mockito.Mockito;
13 | import org.springframework.beans.factory.annotation.Autowired;
14 |
15 | class NestedTest extends BaseTest {
16 |
17 | @MockInBean(MyService.class)
18 | private ThirdPartyApiService thirdPartyApiService;
19 |
20 | @SpyInBean(MyService.class)
21 | private ExpensiveProcessor expensiveProcessor;
22 |
23 | @Autowired
24 | private MyService myService;
25 |
26 | @Nested
27 | class Inner1 {
28 |
29 | @Test
30 | void resultReturnMockedValue() {
31 | final Object somethingExpensive = new Object();
32 | Mockito.when(expensiveProcessor.returnSomethingExpensive()).thenReturn(somethingExpensive);
33 | myService.doSomething();
34 | Mockito.verify(thirdPartyApiService).doSomethingOnThirdPartyApi(somethingExpensive);
35 | }
36 |
37 | @Test
38 | public void test2() {
39 | Mockito.when(expensiveProcessor.returnSomethingExpensive()).thenThrow(RuntimeException.class);
40 | Assertions.assertThrows(RuntimeException.class, () -> myService.doSomething());
41 | }
42 |
43 | @Nested
44 | class NestedInner1 {
45 |
46 | @Test
47 | void resultReturnMockedValue() {
48 | final Object somethingExpensive = new Object();
49 | Mockito.when(expensiveProcessor.returnSomethingExpensive()).thenReturn(somethingExpensive);
50 | myService.doSomething();
51 | Mockito.verify(thirdPartyApiService).doSomethingOnThirdPartyApi(somethingExpensive);
52 | }
53 |
54 | }
55 | }
56 |
57 | @Nested
58 | class Inner2 {
59 |
60 | @Test
61 | void resultReturnMockedValue() {
62 | final Object somethingExpensive = new Object();
63 | Mockito.when(expensiveProcessor.returnSomethingExpensive()).thenReturn(somethingExpensive);
64 | myService.doSomething();
65 | Mockito.verify(thirdPartyApiService).doSomethingOnThirdPartyApi(somethingExpensive);
66 | }
67 | }
68 |
69 | @Test
70 | void resultReturnMockedValue() {
71 | final Object somethingExpensive = new Object();
72 | Mockito.when(expensiveProcessor.returnSomethingExpensive()).thenReturn(somethingExpensive);
73 | myService.doSomething();
74 | Mockito.verify(thirdPartyApiService).doSomethingOnThirdPartyApi(somethingExpensive);
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/NestedTopLevelTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import org.junit.jupiter.api.Nested;
4 | import org.junit.jupiter.api.Test;
5 |
6 | @Nested
7 | class NestedTopLevelTest extends BaseTest {
8 |
9 | @Test
10 | public void test() {
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/ProxyTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import com.teketik.test.mockinbean.MockInBean;
4 | import com.teketik.test.mockinbean.test.components.InterceptedComponent;
5 | import com.teketik.test.mockinbean.test.components.MockableComponent1;
6 |
7 | import org.junit.jupiter.api.Assertions;
8 | import org.junit.jupiter.api.Test;
9 | import org.mockito.Mockito;
10 |
11 | import java.util.concurrent.atomic.AtomicBoolean;
12 |
13 | import javax.annotation.Resource;
14 |
15 | class ProxyTest extends BaseTest {
16 |
17 | @MockInBean(InterceptedComponent.class)
18 | private MockableComponent1 mockableComponent1;
19 |
20 | @Resource
21 | private InterceptedComponent interceptedComponent;
22 |
23 | @Test
24 | public void test() {
25 | final AtomicBoolean called = new AtomicBoolean();
26 | Mockito.doAnswer(a -> {
27 | called.set(true);
28 | return null;
29 | }).when(mockableComponent1).doSomething();
30 | interceptedComponent.process();
31 | Assertions.assertTrue(called.get());
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/SpyInBeanTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import com.teketik.test.mockinbean.SpyInBean;
4 | import com.teketik.test.mockinbean.SpyInBeans;
5 | import com.teketik.test.mockinbean.test.components.MockableComponent1;
6 | import com.teketik.test.mockinbean.test.components.MockableComponent2;
7 | import com.teketik.test.mockinbean.test.components.TestComponent1;
8 | import com.teketik.test.mockinbean.test.components.TestComponent2;
9 |
10 | import org.junit.jupiter.api.Assertions;
11 | import org.junit.jupiter.api.Test;
12 | import org.springframework.test.util.ReflectionTestUtils;
13 |
14 | class SpyInBeanTest extends MockInBeanBaseTest {
15 |
16 | @SpyInBeans({
17 | @SpyInBean(TestComponent1.class),
18 | @SpyInBean(TestComponent2.class)
19 | })
20 | private MockableComponent1 mockableComponent1;
21 |
22 | @SpyInBean(TestComponent1.class)
23 | private MockableComponent2 mockableComponent2;
24 |
25 | /**
26 | * Note: Will fail if run individually, run as a class.
27 | */
28 | @Test
29 | public void test() {
30 | Assertions.assertTrue(TestUtils.isSpy(mockableComponent1));
31 | Assertions.assertSame(mockableComponent1, ReflectionTestUtils.getField(testComponent1, "mockableComponent1"));
32 | Assertions.assertSame(mockableComponent1, ReflectionTestUtils.getField(testComponent2, "mockableComponent1"));
33 |
34 | Assertions.assertTrue(TestUtils.isSpy(mockableComponent2));
35 | Assertions.assertSame(mockableComponent2, ReflectionTestUtils.getField(testComponent1, "mockableComponent2"));
36 | Assertions.assertNotSame(mockableComponent2, ReflectionTestUtils.getField(testComponent2, "mockableComponent2"));
37 | Assertions.assertFalse(TestUtils.isMockOrSpy(ReflectionTestUtils.getField(testComponent2, "mockableComponent2")));
38 | }
39 |
40 | @Override
41 | MockableComponent1 getMockableComponent1() {
42 | return mockableComponent1;
43 | }
44 |
45 | @Override
46 | MockableComponent2 getMockableComponent2() {
47 | return mockableComponent2;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/TestApplication.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import org.springframework.boot.autoconfigure.SpringBootApplication;
4 |
5 | @SpringBootApplication
6 | class TestApplication {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/TestInheritance1Test.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | class TestInheritance1Test extends TestInheritanceBaseTest {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/TestInheritance2Test.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | class TestInheritance2Test extends TestInheritanceBaseTest {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/TestInheritanceBaseTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import com.teketik.test.mockinbean.MockInBean;
4 | import com.teketik.test.mockinbean.MockInBeans;
5 | import com.teketik.test.mockinbean.SpyInBean;
6 | import com.teketik.test.mockinbean.test.components.MockableComponent1;
7 | import com.teketik.test.mockinbean.test.components.MockableComponent2;
8 | import com.teketik.test.mockinbean.test.components.TestComponent1;
9 | import com.teketik.test.mockinbean.test.components.TestComponent2;
10 |
11 | import org.junit.jupiter.api.Assertions;
12 | import org.junit.jupiter.api.Test;
13 | import org.springframework.test.util.ReflectionTestUtils;
14 |
15 | import javax.annotation.Resource;
16 |
17 | abstract class TestInheritanceBaseTest extends BaseTest {
18 |
19 | @MockInBeans({
20 | @MockInBean(TestComponent1.class),
21 | @MockInBean(TestComponent2.class)
22 | })
23 | protected MockableComponent1 mockableComponent1;
24 |
25 | @SpyInBean(TestComponent1.class)
26 | protected MockableComponent2 mockableComponent2;
27 |
28 | @Resource
29 | private TestComponent1 testComponent1;
30 |
31 | @Resource
32 | private TestComponent2 testComponent2;
33 |
34 | @Test
35 | public void test() {
36 | Assertions.assertTrue(TestUtils.isMock(mockableComponent1));
37 | Assertions.assertSame(mockableComponent1, ReflectionTestUtils.getField(testComponent1, "mockableComponent1"));
38 | Assertions.assertSame(mockableComponent1, ReflectionTestUtils.getField(testComponent2, "mockableComponent1"));
39 |
40 | Assertions.assertTrue(TestUtils.isSpy(mockableComponent2));
41 | Assertions.assertSame(mockableComponent2, ReflectionTestUtils.getField(testComponent1, "mockableComponent2"));
42 | Assertions.assertNotSame(mockableComponent2, ReflectionTestUtils.getField(testComponent2, "mockableComponent2"));
43 | Assertions.assertFalse(TestUtils.isMockOrSpy(ReflectionTestUtils.getField(testComponent2, "mockableComponent2")));
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/TestInterface.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import com.teketik.test.mockinbean.MockInBean;
4 | import com.teketik.test.mockinbean.test.components.TestComponent1;
5 | import com.teketik.test.mockinbean.test.components.TestComponentInterface;
6 |
7 | import org.junit.jupiter.api.Assertions;
8 | import org.junit.jupiter.api.Test;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.test.util.ReflectionTestUtils;
11 |
12 | public class TestInterface extends BaseTest {
13 |
14 | @Autowired
15 | private TestComponentInterface testComponentInterface;
16 |
17 | @MockInBean(TestComponentInterface.class)
18 | private TestComponent1 testComponent1;
19 |
20 | @Test
21 | public void test() {
22 | Assertions.assertTrue(TestUtils.isMock(testComponent1));
23 | Assertions.assertSame(testComponent1, ReflectionTestUtils.getField(testComponentInterface, "testComponent1"));
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/TestUtils.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import org.mockito.Mock;
4 | import org.mockito.MockingDetails;
5 | import org.mockito.Mockito;
6 | import org.mockito.Spy;
7 |
8 | abstract class TestUtils {
9 |
10 | private TestUtils() {}
11 |
12 | /**
13 | * @param object
14 | * @return true if {@code object} is a {@link Mock} (and not a {@link Spy}!!)
15 | */
16 | public static boolean isMock(Object object) {
17 | final MockingDetails mockingDetails = Mockito.mockingDetails(object);
18 | return mockingDetails.isMock() && !mockingDetails.isSpy();
19 | }
20 |
21 | /**
22 | * @param object
23 | * @return true if {@code object} is a {@link Spy}
24 | */
25 | public static boolean isSpy(Object object) {
26 | return Mockito.mockingDetails(object).isSpy();
27 | }
28 |
29 | /**
30 | * @param object
31 | * @return true if {@code object} is a {@link Mock} or a {@link Spy}.
32 | */
33 | public static boolean isMockOrSpy(Object object) {
34 | return Mockito.mockingDetails(object).isMock();
35 | }
36 |
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/VerifyAdvisedSpyInBeanTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test;
2 |
3 | import static org.mockito.Mockito.verify;
4 |
5 | import com.teketik.test.mockinbean.SpyInBean;
6 | import com.teketik.test.mockinbean.test.VerifyAdvisedSpyInBeanTest.Config.AnAspect;
7 | import com.teketik.test.mockinbean.test.VerifyAdvisedSpyInBeanTest.Config.LoggingService;
8 | import com.teketik.test.mockinbean.test.VerifyAdvisedSpyInBeanTest.Config.ProviderService;
9 |
10 | import org.aspectj.lang.annotation.Aspect;
11 | import org.aspectj.lang.annotation.Before;
12 | import org.junit.jupiter.api.Assertions;
13 | import org.junit.jupiter.api.Test;
14 | import org.mockito.Mockito;
15 | import org.springframework.beans.factory.annotation.Autowired;
16 | import org.springframework.boot.test.context.SpringBootTest;
17 | import org.springframework.context.ApplicationContext;
18 | import org.springframework.core.Ordered;
19 | import org.springframework.stereotype.Component;
20 | import org.springframework.stereotype.Service;
21 | import org.springframework.test.context.TestContext;
22 | import org.springframework.test.context.TestExecutionListener;
23 | import org.springframework.test.context.TestExecutionListeners;
24 | import org.springframework.test.context.TestExecutionListeners.MergeMode;
25 | import org.springframework.test.util.ReflectionTestUtils;
26 |
27 | import java.util.concurrent.atomic.AtomicInteger;
28 |
29 | /**
30 | * Covering test case from https://github.com/inkassso/mock-in-bean-issue-23/blob/master/src/test/java/com/github/inkassso/mockinbean/issue23/service/BrokenLoggingServiceTest1_SpyInBean.java
31 | */
32 | @TestExecutionListeners(value = {VerifyAdvisedSpyInBeanTest.class}, mergeMode = MergeMode.MERGE_WITH_DEFAULTS)
33 | @SpringBootTest
34 | public class VerifyAdvisedSpyInBeanTest implements TestExecutionListener, Ordered {
35 |
36 | @org.springframework.boot.test.context.TestConfiguration
37 | static class Config {
38 |
39 | @Aspect
40 | @Component
41 | public class AnAspect {
42 |
43 | private final AtomicInteger invocationCounter = new AtomicInteger();
44 |
45 | @Before("execution(* com.teketik.test.mockinbean.test.VerifyAdvisedSpyInBeanTest.Config.ProviderService.provideValue())")
46 | public void run() {
47 | invocationCounter.incrementAndGet();
48 | }
49 | }
50 |
51 | @Service
52 | public class ProviderService {
53 | public String provideValue() {
54 | return "";
55 | }
56 | }
57 |
58 | @Component
59 | public class LoggingService {
60 |
61 | @Autowired
62 | private ProviderService providerService;
63 |
64 | public String logCurrentValue() {
65 | return providerService.provideValue();
66 | }
67 | }
68 | }
69 |
70 | @Autowired
71 | protected LoggingService loggingService;
72 |
73 | @Autowired
74 | protected AnAspect anAspect;
75 |
76 | @SpyInBean(LoggingService.class)
77 | private ProviderService providerService;
78 |
79 | @Test
80 | void testAspectInvocation() {
81 | int initialCounterValue = anAspect.invocationCounter.get();
82 | loggingService.logCurrentValue();
83 | Assertions.assertEquals(initialCounterValue + 1, anAspect.invocationCounter.get());
84 | verify(providerService).provideValue();
85 | Assertions.assertEquals(initialCounterValue + 2, anAspect.invocationCounter.get());
86 | }
87 |
88 | @Test
89 | void testSpyAnswer() {
90 | Mockito.doAnswer(i -> "value").when(providerService).provideValue();
91 | Assertions.assertEquals("value", loggingService.logCurrentValue());
92 | }
93 |
94 | @Override
95 | public void afterTestClass(TestContext testContext) throws Exception {
96 | final ApplicationContext applicationContext = testContext.getApplicationContext();
97 |
98 | //ensure context clean
99 | final Object loggingServiceBean = applicationContext.getBean(LoggingService.class);
100 | final Object providerServiceInBean = ReflectionTestUtils.getField(loggingServiceBean, "providerService");
101 | Assertions.assertFalse(TestUtils.isMockOrSpy(providerServiceInBean));
102 | Assertions.assertSame(applicationContext.getBean(ProviderService.class), providerServiceInBean);
103 |
104 | //ensure aspect invoked
105 | final AnAspect anAspect = applicationContext.getBean(AnAspect.class);
106 | Assertions.assertEquals(4, anAspect.invocationCounter.get());
107 | }
108 |
109 | @Override
110 | public int getOrder() {
111 | return Integer.MAX_VALUE;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/annotations/MockInBeanWithCustomName.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.annotations;
2 |
3 | import com.teketik.test.mockinbean.MockInBean;
4 | import com.teketik.test.mockinbean.test.components.TestComponent1;
5 |
6 | import java.lang.annotation.ElementType;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.RetentionPolicy;
9 | import java.lang.annotation.Target;
10 |
11 | @Target({ElementType.FIELD})
12 | @Retention(RetentionPolicy.RUNTIME)
13 | @MockInBean(TestComponent1.class)
14 | public @interface MockInBeanWithCustomName {
15 | }
16 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/annotations/MockInMultipleComponents.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.annotations;
2 |
3 | import com.teketik.test.mockinbean.MockInBean;
4 | import com.teketik.test.mockinbean.test.components.TestComponent1;
5 | import com.teketik.test.mockinbean.test.components.TestComponent2;
6 |
7 | import java.lang.annotation.ElementType;
8 | import java.lang.annotation.Retention;
9 | import java.lang.annotation.RetentionPolicy;
10 | import java.lang.annotation.Target;
11 |
12 | @Target({ElementType.FIELD})
13 | @Retention(RetentionPolicy.RUNTIME)
14 | @MockInBean(TestComponent1.class)
15 | @MockInBean(TestComponent2.class)
16 | public @interface MockInMultipleComponents {
17 | }
18 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/components/GenericMockableComponent.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.components;
2 |
3 | import org.springframework.stereotype.Component;
4 |
5 | @Component
6 | public class GenericMockableComponent {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/components/GenericTestComponent.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.components;
2 |
3 | import org.springframework.stereotype.Component;
4 |
5 | import javax.annotation.Resource;
6 |
7 | @Component
8 | public class GenericTestComponent {
9 |
10 | @Resource
11 | private GenericMockableComponent genericMockableComponent;
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/components/InterceptedComponent.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.components;
2 |
3 | import org.springframework.stereotype.Component;
4 |
5 | import javax.annotation.Resource;
6 |
7 | @Component
8 | public class InterceptedComponent {
9 |
10 | @Resource
11 | private MockableComponent1 mockableComponent1;
12 |
13 | public void process() {
14 | mockableComponent1.doSomething();
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/components/InterceptedComponentAspect.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.components;
2 |
3 | import org.aspectj.lang.annotation.Aspect;
4 | import org.aspectj.lang.annotation.Before;
5 | import org.springframework.stereotype.Component;
6 |
7 | @Aspect
8 | @Component
9 | public class InterceptedComponentAspect {
10 |
11 | @Before("execution(* com.teketik.test.mockinbean.test.components.InterceptedComponent.*(..))")
12 | public void intercept() {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/components/MockableComponent1.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.components;
2 |
3 | import org.springframework.stereotype.Component;
4 |
5 | @Component
6 | public class MockableComponent1 {
7 |
8 | public void doSomething() {
9 | throw new UnsupportedOperationException();
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/components/MockableComponent2.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.components;
2 |
3 | import org.springframework.stereotype.Component;
4 |
5 | @Component
6 | public class MockableComponent2 {
7 |
8 | public void doSomething() {
9 | throw new UnsupportedOperationException();
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/components/MockableComponentExtending2.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.components;
2 |
3 | import org.springframework.stereotype.Component;
4 |
5 | @Component
6 | public class MockableComponentExtending2 extends MockableComponent2 {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/components/TestComponent1.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.components;
2 |
3 | import org.springframework.stereotype.Component;
4 |
5 | @Component
6 | public class TestComponent1 extends TestComponentBase {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/components/TestComponent2.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.components;
2 |
3 | import org.springframework.stereotype.Component;
4 |
5 | @Component
6 | public class TestComponent2 extends TestComponentBase {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/components/TestComponent3.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.components;
2 |
3 | import org.springframework.stereotype.Component;
4 |
5 | @Component
6 | public class TestComponent3 extends TestComponentBase {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/components/TestComponentBase.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.components;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 |
5 | public abstract class TestComponentBase {
6 |
7 | @Autowired
8 | private MockableComponent1 mockableComponent1;
9 |
10 | @Autowired
11 | private MockableComponent2 mockableComponent2;
12 |
13 | public void doWith1() {
14 | mockableComponent1.doSomething();
15 | }
16 |
17 | public void doWith2() {
18 | mockableComponent2.doSomething();
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/components/TestComponentInterface.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.components;
2 |
3 | public interface TestComponentInterface {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/components/TestComponentInterfaceImpl.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.components;
2 |
3 | import org.springframework.stereotype.Component;
4 |
5 | import javax.annotation.Resource;
6 |
7 | @Component
8 | public class TestComponentInterfaceImpl implements TestComponentInterface {
9 |
10 | @Resource
11 | private TestComponent1 testComponent1;
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/components/TestComponentOn3.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.components;
2 |
3 | import org.springframework.stereotype.Component;
4 |
5 | @Component
6 | public class TestComponentOn3 extends TestComponent3 {
7 |
8 |
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/components/TestComponentWithMultipleSameType.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.components;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.stereotype.Component;
5 |
6 | @Component
7 | public class TestComponentWithMultipleSameType {
8 |
9 | @Autowired
10 | private MockableComponent1 mockableComponent1a;
11 |
12 | @Autowired
13 | private MockableComponent1 mockableComponent1b;
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/example/ExpensiveProcessor.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.example;
2 |
3 | import org.springframework.stereotype.Component;
4 |
5 | @Component
6 | public class ExpensiveProcessor {
7 |
8 | public Object returnSomethingExpensive() {
9 | return new Object();
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/example/MyService.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.example;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.stereotype.Service;
5 |
6 | @Service
7 | public class MyService {
8 |
9 | @Autowired
10 | protected ThirdPartyApiService thirdPartyService;
11 |
12 | @Autowired
13 | protected ExpensiveProcessor expensiveProcessor;
14 |
15 | public void doSomething() {
16 | final Object somethingExpensive = expensiveProcessor.returnSomethingExpensive();
17 | thirdPartyService.doSomethingOnThirdPartyApi(somethingExpensive);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/example/MyServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.example;
2 |
3 | import com.teketik.test.mockinbean.MockInBean;
4 | import com.teketik.test.mockinbean.SpyInBean;
5 |
6 | import org.junit.jupiter.api.Test;
7 | import org.mockito.Mockito;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.boot.test.context.SpringBootTest;
10 |
11 | @SpringBootTest
12 | public class MyServiceTest {
13 |
14 | @MockInBean(MyService.class)
15 | private ThirdPartyApiService thirdPartyApiService;
16 |
17 | @SpyInBean(MyService.class)
18 | private ExpensiveProcessor expensiveProcessor;
19 |
20 | @Autowired
21 | private MyService myService;
22 |
23 | @Test
24 | public void test() {
25 | final Object somethingExpensive = new Object();
26 | Mockito.when(expensiveProcessor.returnSomethingExpensive()).thenReturn(somethingExpensive);
27 | myService.doSomething();
28 | Mockito.verify(thirdPartyApiService).doSomethingOnThirdPartyApi(somethingExpensive);
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/test/java/com/teketik/test/mockinbean/test/example/ThirdPartyApiService.java:
--------------------------------------------------------------------------------
1 | package com.teketik.test.mockinbean.test.example;
2 |
3 | import org.springframework.stereotype.Service;
4 |
5 | @Service
6 | public class ThirdPartyApiService {
7 |
8 | public void doSomethingOnThirdPartyApi(Object somethingExpensive) {
9 | throw new UnsupportedOperationException();
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------