package ru.vyarus.dropwizard.guice.test.jupiter.ext; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.inject.BindingAnnotation; import com.google.inject.Injector; import com.google.inject.Key; import io.dropwizard.Application; import io.dropwizard.Configuration; import io.dropwizard.testing.DropwizardTestSupport; 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.platform.commons.support.AnnotationSupport; import ru.vyarus.dropwizard.guice.injector.lookup.InjectorLookup; import ru.vyarus.dropwizard.guice.test.ClientSupport; import ru.vyarus.dropwizard.guice.test.jupiter.param.Jit; import javax.inject.Qualifier; import java.lang.annotation.Annotation; import java.lang.reflect.Parameter; import java.util.List; import java.util.Optional; /** * Base class for junit 5 extensions. Supports direct injection of test parameters: * <ul> * <li>{@link Application} or exact application class</li> * <li>{@link ObjectMapper}</li> * <li>{@link ClientSupport} application web client helper</li> * <li>Any existing guice binding (possibly with qualifier annotation or generified)</li> * <li>{@link Jit} annotated parameter will be obtained from guice context (assume JIT binding)</li> * </ul> * Overall, it provides everything {@link DropwizardTestSupport} provides plus guice-managed beans. * * @author Vyacheslav Rusakov * @since 29.04.2020 */ public abstract class TestParametersSupport implements ParameterResolver { private final List<Class<?>> supportedClasses = ImmutableList.of( ObjectMapper.class, ClientSupport.class); @Override @SuppressWarnings("checkstyle:ReturnCount") public boolean supportsParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) throws ParameterResolutionException { final Parameter parameter = parameterContext.getParameter(); if (parameter.getAnnotations().length > 0) { if (AnnotationSupport.isAnnotated(parameter, Jit.class)) { return true; } else if (!isQualifierAnnotation(parameter.getAnnotations())) { // if any other annotation declared on the parameter - skip it (possibly other extension's parameter) return false; } } final Class<?> type = parameter.getType(); if (Application.class.isAssignableFrom(type) || Configuration.class.isAssignableFrom(type)) { // special case when exact app or configuration class used return true; } else { for (Class<?> cls : supportedClasses) { if (type.equals(cls)) { return true; } } } // declared guice binding (by class only) return getInjector(extensionContext) .map(it -> it.getExistingBinding(getKey(parameter)) != null) .orElse(false); } @Override @SuppressWarnings("checkstyle:ReturnCount") public Object resolveParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) throws ParameterResolutionException { final Parameter parameter = parameterContext.getParameter(); final Class<?> type = parameter.getType(); if (ClientSupport.class.equals(type)) { return getClient(extensionContext); } final DropwizardTestSupport<?> support = Preconditions.checkNotNull(getSupport(extensionContext)); if (Application.class.isAssignableFrom(type)) { return support.getApplication(); } if (ObjectMapper.class.equals(type)) { return support.getObjectMapper(); } return InjectorLookup.getInjector(support.getApplication()) .map(it -> it.getInstance(getKey(parameter))) .get(); } /** * @param extensionContext junit extension context * @return dropwizard test support object assigned to test instance or null */ protected abstract DropwizardTestSupport<?> getSupport(ExtensionContext extensionContext); /** * @param extensionContext junit extension context * @return client factory object assigned to test instance (never null) */ protected abstract ClientSupport getClient(ExtensionContext extensionContext); /** * @param extensionContext junit extension context * @return application injector or null */ protected abstract Optional<Injector> getInjector(ExtensionContext extensionContext); private boolean isQualifierAnnotation(final Annotation... annotations) { final Annotation ann = annotations[0]; return annotations.length == 1 && (AnnotationSupport.isAnnotated(ann.annotationType(), Qualifier.class) || AnnotationSupport.isAnnotated(ann.annotationType(), BindingAnnotation.class)); } private Key<?> getKey(final Parameter parameter) { final Key<?> key; if (parameter.getAnnotations().length > 0 && !AnnotationSupport.isAnnotated(parameter, Jit.class)) { // qualified bean key = Key.get(parameter.getParameterizedType(), parameter.getAnnotations()[0]); } else { key = Key.get(parameter.getParameterizedType()); } return key; } }