Skip to content

Commit

Permalink
Fix MockBean to work with ArgumentMatcher (helidon-io#9398)
Browse files Browse the repository at this point in the history
- 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

(cherry picked from commit 6a93b66)
  • Loading branch information
romain-grecourt authored and danielkec committed Dec 20, 2024
1 parent 70de243 commit 4fe8cab
Show file tree
Hide file tree
Showing 6 changed files with 372 additions and 229 deletions.
29 changes: 29 additions & 0 deletions microprofile/testing/mocking/etc/spotbugs/exclude.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2024 Oracle and/or its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<FindBugsFilter
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://github.com/spotbugs/filter/3.0.0"
xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd">

<Match>
<Class name="io.helidon.microprofile.testing.mocking.MockBeansCdiExtension"/>
<Bug pattern="RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT"/>
</Match>
</FindBugsFilter>
4 changes: 4 additions & 0 deletions microprofile/testing/mocking/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
Integration with Mocking to support tests with CDI injection
</description>

<properties>
<spotbugs.exclude>etc/spotbugs/exclude.xml</spotbugs.exclude>
</properties>

<dependencies>
<dependency>
<groupId>jakarta.enterprise</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,23 @@

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.enterprise.inject.Instance;
import jakarta.enterprise.inject.literal.InjectLiteral;
import jakarta.enterprise.inject.spi.AfterBeanDiscovery;
import jakarta.enterprise.inject.spi.AfterDeploymentValidation;
import jakarta.enterprise.inject.spi.AnnotatedParameter;
import jakarta.enterprise.inject.spi.Bean;
import jakarta.enterprise.inject.spi.BeanManager;
import jakarta.enterprise.inject.spi.Extension;
import jakarta.enterprise.inject.spi.ProcessAnnotatedType;
import jakarta.enterprise.inject.spi.WithAnnotations;
import jakarta.enterprise.inject.spi.configurator.AnnotatedConstructorConfigurator;
import jakarta.enterprise.inject.spi.configurator.AnnotatedFieldConfigurator;
import jakarta.enterprise.inject.spi.configurator.AnnotatedTypeConfigurator;
import org.mockito.MockSettings;
import org.mockito.Mockito;

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

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

void processMockBean(@Observes @WithAnnotations(MockBean.class) ProcessAnnotatedType<?> obj) throws Exception {
var configurator = obj.configureAnnotatedType();
configurator.fields().forEach(field -> {
void processMockBean(@Observes @WithAnnotations(MockBean.class) ProcessAnnotatedType<?> obj) {
AnnotatedTypeConfigurator<?> configurator = obj.configureAnnotatedType();
for (AnnotatedFieldConfigurator<?> field : configurator.fields()) {
MockBean mockBean = field.getAnnotated().getAnnotation(MockBean.class);
if (mockBean != null) {
Field f = field.getAnnotated().getJavaMember();
// Adds @Inject to be more user friendly
// make @Inject optional
field.add(InjectLiteral.INSTANCE);
Class<?> fieldType = f.getType();
mocks.put(fieldType, mockBean);
mocks.put(f.getType(), mockBean);
}
});
configurator.constructors().forEach(constructor -> {
processMockBeanParameters(constructor.getAnnotated().getParameters());
});
}

private void processMockBeanParameters(List<? extends AnnotatedParameter<?>> parameters) {
parameters.stream().forEach(parameter -> {
MockBean mockBean = parameter.getAnnotation(MockBean.class);
if (mockBean != null) {
Class<?> parameterType = parameter.getJavaParameter().getType();
mocks.put(parameterType, mockBean);
}
for (AnnotatedConstructorConfigurator<?> ctor : configurator.constructors()) {
for (AnnotatedParameter<?> parameter : ctor.getAnnotated().getParameters()) {
MockBean mockBean = parameter.getAnnotation(MockBean.class);
if (mockBean != null) {
Class<?> parameterType = parameter.getJavaParameter().getType();
mocks.put(parameterType, mockBean);
}
}
});
}
}

void registerOtherBeans(@Observes AfterBeanDiscovery event, BeanManager beanManager) {
// Register all mocks
mocks.entrySet().forEach(entry -> {
event.addBean()
.addType(entry.getKey())
void registerOtherBeans(@Observes AfterBeanDiscovery event) {
// register mocks
mocks.forEach((key, value) -> event.addBean()
.addTransitiveTypeClosure(key)
.scope(ApplicationScoped.class)
.alternative(true)
.createWith(inst -> {
Set<Bean<?>> beans = beanManager.getBeans(MockSettings.class);
if (!beans.isEmpty()) {
Bean<?> bean = beans.iterator().next();
MockSettings mockSettings = (MockSettings) beanManager.getReference(bean, MockSettings.class,
beanManager.createCreationalContext(null));
return Mockito.mock(entry.getKey(), mockSettings);
} else {
return Mockito.mock(entry.getKey(), Mockito.withSettings().defaultAnswer(entry.getValue().answer()));
}
.produceWith(i -> {
Instance<MockSettings> msi = i.select(MockSettings.class);
MockSettings settings = msi.isUnsatisfied()
? Mockito.withSettings().defaultAnswer(value.answer())
: msi.get();
return Mockito.mock(key, settings);
})
.priority(0);
});
.priority(0));
}

void initializeBeans(@Observes AfterDeploymentValidation event, BeanManager manager) {
for (Class<?> key : mocks.keySet()) {
for (Bean<?> bean : manager.getBeans(key)) {
// call toString() to force the beans to be initialized
// noinspection ResultOfMethodCallIgnored
manager.getReference(bean, key, manager.createCreationalContext(bean)).toString();
}
}
}
}
Loading

0 comments on commit 4fe8cab

Please sign in to comment.