org.jboss.weld.junit5.WeldJunit5Extension Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source
* Copyright 2017, Red Hat, Inc., and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.
*/
package org.jboss.weld.junit5;
import static org.jboss.weld.junit5.ExtensionContextUtils.getContainerFromStore;
import static org.jboss.weld.junit5.ExtensionContextUtils.getEnrichersFromStore;
import static org.jboss.weld.junit5.ExtensionContextUtils.getExplicitInjectionInfoFromStore;
import static org.jboss.weld.junit5.ExtensionContextUtils.getInitiatorFromStore;
import static org.jboss.weld.junit5.ExtensionContextUtils.setContainerToStore;
import static org.jboss.weld.junit5.ExtensionContextUtils.setEnrichersToStore;
import static org.jboss.weld.junit5.ExtensionContextUtils.setExplicitInjectionInfoToStore;
import static org.jboss.weld.junit5.ExtensionContextUtils.setInitiatorToStore;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
import jakarta.enterprise.inject.spi.BeanManager;
import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.inject.WeldInstance;
import org.jboss.weld.util.collections.ImmutableList;
import org.junit.jupiter.api.RepetitionInfo;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestReporter;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
/**
* JUnit 5 extension allowing to bootstrap Weld SE container for each @Test method (or once per test class
* if running {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_CLASS}) and tear it down afterwards. Also allows
* injecting CDI beans as parameters to @Test methods and resolves all @Inject fields in test class.
*
*
* If no {@link WeldInitiator} field annotated with {@link WeldSetup} is present on a test class, all service providers of
* {@link WeldJunitEnricher} interface are used to enrich the default test environment.
*
*
*
* @ExtendWith(WeldJunit5Extension.class)
* public class SimpleTest {
*
* // Injected automatically
* @Inject
* Foo foo;
*
* @Test
* public void testFoo() {
* // Weld container is started automatically
* assertEquals("baz", foo.getBaz());
* }
* }
*
*
* @author Matej Novotny
* @see EnableWeld
* @see WeldJunitEnricher
*/
public class WeldJunit5Extension implements AfterAllCallback, BeforeAllCallback,
BeforeEachCallback, AfterEachCallback, ParameterResolver {
// global system property
public static final String GLOBAL_EXPLICIT_PARAM_INJECTION = "org.jboss.weld.junit5.explicitParamInjection";
private static void storeExplicitParamResolutionInformation(ExtensionContext ec) {
// check system property which may have set the global explicit param injection
boolean globalSettings = Boolean.parseBoolean(System.getProperty(GLOBAL_EXPLICIT_PARAM_INJECTION, "false"));
if (globalSettings) {
setExplicitInjectionInfoToStore(ec, true);
return;
}
// check class-level annotation
Class> inspectedTestClass = ec.getRequiredTestClass();
ExplicitParamInjection explicitParamInjection = inspectedTestClass.getAnnotation(ExplicitParamInjection.class);
if (explicitParamInjection != null) {
setExplicitInjectionInfoToStore(ec, explicitParamInjection.value());
} else {
// if not found, it can still be a nested class
// inspect enclosing classes until first annotation is found or until we hit top-level class
inspectedTestClass = inspectedTestClass.getEnclosingClass();
while (inspectedTestClass != null && explicitParamInjection == null) {
explicitParamInjection = inspectedTestClass.getAnnotation(ExplicitParamInjection.class);
if (explicitParamInjection != null) {
setExplicitInjectionInfoToStore(ec, explicitParamInjection.value());
}
inspectedTestClass = inspectedTestClass.getEnclosingClass();
}
}
}
@Override
public void beforeAll(ExtensionContext context) {
// we are storing them into root context, hence only needs to be done once per test suite
if (getEnrichersFromStore(context) == null) {
ImmutableList.Builder enrichers = ImmutableList.builder();
ServiceLoader.load(WeldJunitEnricher.class).forEach(enrichers::add);
setEnrichersToStore(context, enrichers.build());
}
// if the lifecycle is per-class, then we want to start container here
startWeldContainerIfAppropriate(PER_CLASS, context);
}
@Override
public void beforeEach(ExtensionContext extensionContext) {
startWeldContainerIfAppropriate(PER_METHOD, extensionContext);
}
@Override
public void afterEach(ExtensionContext context) {
if (determineTestLifecycle(context).equals(PER_METHOD)) {
WeldInitiator initiator = getInitiatorFromStore(context);
if (initiator != null) {
initiator.shutdownWeld();
}
}
}
@Override
public void afterAll(ExtensionContext context) {
if (determineTestLifecycle(context).equals(PER_CLASS)) {
WeldInitiator initiator = getInitiatorFromStore(context);
if (initiator != null) {
initiator.shutdownWeld();
}
}
}
protected void weldInit(ExtensionContext context, Weld weld, WeldInitiator.Builder weldInitiatorBuilder) {
weld.addPackage(false, context.getRequiredTestClass());
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
// we did our checks in supportsParameter() method, now we can do simple resolution
if (getContainerFromStore(extensionContext) != null) {
List qualifiers = resolveQualifiers(parameterContext,
getContainerFromStore(extensionContext).getBeanManager());
return getContainerFromStore(extensionContext)
.select(parameterContext.getParameter().getParameterizedType(),
qualifiers.toArray(new Annotation[qualifiers.size()]))
.get();
}
return null;
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
// do not attempt to resolve JUnit 5 built-in parameters
if (isJUnitResolvedParameter(parameterContext)) {
return false;
}
// if weld container isn't up yet or if it's not Method, we don't resolve it
if (getContainerFromStore(extensionContext) == null
|| (!(parameterContext.getDeclaringExecutable() instanceof Method))) {
return false;
}
List qualifiers = resolveQualifiers(parameterContext,
getContainerFromStore(extensionContext).getBeanManager());
// if we require explicit parameter injection (via global settings or annotation) and there are no qualifiers we don't resolve it
// if the method is annotated @ParameterizedTest, we treat it as explicit param injection and require qualifiers
if ((getExplicitInjectionInfoFromStore(extensionContext)
|| methodRequiresExplicitParamInjection(parameterContext)
|| methodIsParameterizedTest(parameterContext))
&& qualifiers.isEmpty()) {
return false;
} else {
// attempt to resolve the bean; at this point we know it should be a CDI bean since it has CDI qualifiers
// if resolution fails, throw an exception
WeldInstance> select = getContainerFromStore(extensionContext).select(
parameterContext.getParameter().getParameterizedType(),
qualifiers.toArray(new Annotation[qualifiers.size()]));
if (!select.isResolvable()) {
throw new ParameterResolutionException(String.format(
"Weld has failed to resolve test parameter [%s] in method [%s].%n" +
"%s dependency has type %s and qualifiers %s.",
parameterContext.getParameter(), parameterContext.getDeclaringExecutable().toGenericString(),
select.isAmbiguous() ? "Ambiguous" : "Unsatisfied",
parameterContext.getParameter().getType().getName(), qualifiers));
}
return true;
}
}
/**
* @see {@code org.junit.jupiter.engine.extension.TestInfoParameterResolver.supportsParameter}
* @see {@code org.junit.jupiter.engine.extension.RepetitionExtension.supportsParameter}
* @see {@code org.junit.jupiter.engine.extension.TestReporterParameterResolver.supportsParameter}
* @see {@code org.junit.jupiter.engine.extension.TempDirectory.supportsParameter}
*/
private boolean isJUnitResolvedParameter(ParameterContext parameterContext) {
Class> type = parameterContext.getParameter().getType();
if (type == TestInfo.class || type == RepetitionInfo.class || type == TestReporter.class) {
return true;
}
if (parameterContext.isAnnotated(TempDir.class)) {
return true;
}
return false;
}
private List resolveQualifiers(ParameterContext pc, BeanManager bm) {
List qualifiers = new ArrayList<>();
if (pc.getParameter().getAnnotations().length == 0) {
return Collections.emptyList();
} else {
for (Annotation annotation : pc.getParameter().getAnnotations()) {
// use BeanManager.isQualifier to be able to detect custom qualifiers which don't need to have @Qualifier
if (bm.isQualifier(annotation.annotationType())) {
qualifiers.add(annotation);
}
}
}
return qualifiers;
}
private boolean methodRequiresExplicitParamInjection(ParameterContext pc) {
ExplicitParamInjection ann = pc.getDeclaringExecutable().getAnnotation(ExplicitParamInjection.class);
if (ann != null) {
return ann.value();
}
return false;
}
private boolean methodIsParameterizedTest(ParameterContext pc) {
return pc.getDeclaringExecutable().getAnnotation(ParameterizedTest.class) != null ? true : false;
}
private TestInstance.Lifecycle determineTestLifecycle(ExtensionContext ec) {
// check the test for org.junit.jupiter.api.TestInstance annotation
TestInstance annotation = ec.getRequiredTestClass().getAnnotation(TestInstance.class);
if (annotation != null) {
return annotation.value();
} else {
return TestInstance.Lifecycle.PER_METHOD;
}
}
private void startWeldContainerIfAppropriate(TestInstance.Lifecycle expectedLifecycle, ExtensionContext context) {
// if the lifecycle is what we expect it to be, start Weld container
if (determineTestLifecycle(context).equals(expectedLifecycle)) {
Object testInstance = context.getRequiredTestInstance();
// store info about explicit param injection, either from global settings or from annotation on the test class
storeExplicitParamResolutionInformation(context);
// iterate through the testInstance, the enclosing instance (in case of nested tests),
// the enclosing instance of the enclosing instance (in cases of twice nested tests) and so on
// until we find a WeldInitiator
final List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy