Skip to content

Commit 6a93b66

Browse files
Fix MockBean to work with ArgumentMatcher (helidon-io#9398)
- Call toString() to force initialization of the mocked instances - Use produceWith instead of createWith - Use addTransitiveTypeClosure instead of addType to support more than just one type - Minor refactoring of the processMockBean method - Add unit test - Re-work HelidonTestNgListener to initialize the testInstance with an extension Fixes helidon-io#9397 Fixes helidon-io#9411
1 parent c120f73 commit 6a93b66

File tree

6 files changed

+372
-229
lines changed

6 files changed

+372
-229
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
Copyright (c) 2024 Oracle and/or its affiliates.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
18+
-->
19+
20+
<FindBugsFilter
21+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
22+
xmlns="https://github.com/spotbugs/filter/3.0.0"
23+
xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd">
24+
25+
<Match>
26+
<Class name="io.helidon.microprofile.testing.mocking.MockBeansCdiExtension"/>
27+
<Bug pattern="RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT"/>
28+
</Match>
29+
</FindBugsFilter>

microprofile/testing/mocking/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
Integration with Mocking to support tests with CDI injection
3333
</description>
3434

35+
<properties>
36+
<spotbugs.exclude>etc/spotbugs/exclude.xml</spotbugs.exclude>
37+
</properties>
38+
3539
<dependencies>
3640
<dependency>
3741
<groupId>jakarta.enterprise</groupId>

microprofile/testing/mocking/src/main/java/io/helidon/microprofile/testing/mocking/MockBeansCdiExtension.java

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,23 @@
1717

1818
import java.lang.reflect.Field;
1919
import java.util.HashMap;
20-
import java.util.List;
2120
import java.util.Map;
22-
import java.util.Set;
2321

2422
import jakarta.enterprise.context.ApplicationScoped;
2523
import jakarta.enterprise.event.Observes;
24+
import jakarta.enterprise.inject.Instance;
2625
import jakarta.enterprise.inject.literal.InjectLiteral;
2726
import jakarta.enterprise.inject.spi.AfterBeanDiscovery;
27+
import jakarta.enterprise.inject.spi.AfterDeploymentValidation;
2828
import jakarta.enterprise.inject.spi.AnnotatedParameter;
2929
import jakarta.enterprise.inject.spi.Bean;
3030
import jakarta.enterprise.inject.spi.BeanManager;
3131
import jakarta.enterprise.inject.spi.Extension;
3232
import jakarta.enterprise.inject.spi.ProcessAnnotatedType;
3333
import jakarta.enterprise.inject.spi.WithAnnotations;
34+
import jakarta.enterprise.inject.spi.configurator.AnnotatedConstructorConfigurator;
35+
import jakarta.enterprise.inject.spi.configurator.AnnotatedFieldConfigurator;
36+
import jakarta.enterprise.inject.spi.configurator.AnnotatedTypeConfigurator;
3437
import org.mockito.MockSettings;
3538
import org.mockito.Mockito;
3639

@@ -41,52 +44,51 @@ public class MockBeansCdiExtension implements Extension {
4144

4245
private final Map<Class<?>, MockBean> mocks = new HashMap<>();
4346

44-
void processMockBean(@Observes @WithAnnotations(MockBean.class) ProcessAnnotatedType<?> obj) throws Exception {
45-
var configurator = obj.configureAnnotatedType();
46-
configurator.fields().forEach(field -> {
47+
void processMockBean(@Observes @WithAnnotations(MockBean.class) ProcessAnnotatedType<?> obj) {
48+
AnnotatedTypeConfigurator<?> configurator = obj.configureAnnotatedType();
49+
for (AnnotatedFieldConfigurator<?> field : configurator.fields()) {
4750
MockBean mockBean = field.getAnnotated().getAnnotation(MockBean.class);
4851
if (mockBean != null) {
4952
Field f = field.getAnnotated().getJavaMember();
50-
// Adds @Inject to be more user friendly
53+
// make @Inject optional
5154
field.add(InjectLiteral.INSTANCE);
52-
Class<?> fieldType = f.getType();
53-
mocks.put(fieldType, mockBean);
55+
mocks.put(f.getType(), mockBean);
5456
}
55-
});
56-
configurator.constructors().forEach(constructor -> {
57-
processMockBeanParameters(constructor.getAnnotated().getParameters());
58-
});
59-
}
60-
61-
private void processMockBeanParameters(List<? extends AnnotatedParameter<?>> parameters) {
62-
parameters.stream().forEach(parameter -> {
63-
MockBean mockBean = parameter.getAnnotation(MockBean.class);
64-
if (mockBean != null) {
65-
Class<?> parameterType = parameter.getJavaParameter().getType();
66-
mocks.put(parameterType, mockBean);
57+
}
58+
for (AnnotatedConstructorConfigurator<?> ctor : configurator.constructors()) {
59+
for (AnnotatedParameter<?> parameter : ctor.getAnnotated().getParameters()) {
60+
MockBean mockBean = parameter.getAnnotation(MockBean.class);
61+
if (mockBean != null) {
62+
Class<?> parameterType = parameter.getJavaParameter().getType();
63+
mocks.put(parameterType, mockBean);
64+
}
6765
}
68-
});
66+
}
6967
}
7068

71-
void registerOtherBeans(@Observes AfterBeanDiscovery event, BeanManager beanManager) {
72-
// Register all mocks
73-
mocks.entrySet().forEach(entry -> {
74-
event.addBean()
75-
.addType(entry.getKey())
69+
void registerOtherBeans(@Observes AfterBeanDiscovery event) {
70+
// register mocks
71+
mocks.forEach((key, value) -> event.addBean()
72+
.addTransitiveTypeClosure(key)
7673
.scope(ApplicationScoped.class)
7774
.alternative(true)
78-
.createWith(inst -> {
79-
Set<Bean<?>> beans = beanManager.getBeans(MockSettings.class);
80-
if (!beans.isEmpty()) {
81-
Bean<?> bean = beans.iterator().next();
82-
MockSettings mockSettings = (MockSettings) beanManager.getReference(bean, MockSettings.class,
83-
beanManager.createCreationalContext(null));
84-
return Mockito.mock(entry.getKey(), mockSettings);
85-
} else {
86-
return Mockito.mock(entry.getKey(), Mockito.withSettings().defaultAnswer(entry.getValue().answer()));
87-
}
75+
.produceWith(i -> {
76+
Instance<MockSettings> msi = i.select(MockSettings.class);
77+
MockSettings settings = msi.isUnsatisfied()
78+
? Mockito.withSettings().defaultAnswer(value.answer())
79+
: msi.get();
80+
return Mockito.mock(key, settings);
8881
})
89-
.priority(0);
90-
});
82+
.priority(0));
83+
}
84+
85+
void initializeBeans(@Observes AfterDeploymentValidation event, BeanManager manager) {
86+
for (Class<?> key : mocks.keySet()) {
87+
for (Bean<?> bean : manager.getBeans(key)) {
88+
// call toString() to force the beans to be initialized
89+
// noinspection ResultOfMethodCallIgnored
90+
manager.getReference(bean, key, manager.createCreationalContext(bean)).toString();
91+
}
92+
}
9193
}
9294
}

0 commit comments

Comments
 (0)