New junit parameter provider from static fields. (#6232)

This commit is contained in:
Michael Buckley 2024-08-26 09:02:44 -04:00 committed by GitHub
parent 709756d68d
commit d9e5760ea7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 160 additions and 0 deletions

View File

@ -0,0 +1,92 @@
package ca.uhn.test.junit;
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.platform.commons.util.ExceptionUtils;
import org.junit.platform.commons.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
import static org.junit.platform.commons.util.AnnotationUtils.findAnnotatedFields;
import static org.junit.platform.commons.util.ReflectionUtils.makeAccessible;
/**
* Register any field annotated with @{@link JunitFieldProvider} as a parameter provider, matching parameters by type.
* Can also be used directly via @RegisterExtension, using values passed to the constructor.
*/
public class JunitFieldParameterProviderExtension implements BeforeAllCallback, BeforeEachCallback, ParameterResolver {
Set<Object> myValues = new HashSet<>();
/**
* Junit constructor for @{@link org.junit.jupiter.api.extension.ExtendWith}
*/
public JunitFieldParameterProviderExtension() {}
/**
* Used for explicit registration.
* @param theValues the values to register as provided junit parameters.
*/
public JunitFieldParameterProviderExtension(Object ...theValues) {
Collections.addAll(myValues, theValues);
}
/**
* Do we have a value matching the parameter type?
*/
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
Class<?> paramType = parameterContext.getParameter().getType();
return myValues.stream().anyMatch(v->paramType.isAssignableFrom(v.getClass()));
}
/**
* Find the first value matching the parameter type.
*/
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
Class<?> paramType = parameterContext.getParameter().getType();
return myValues.stream().filter(v->paramType.isAssignableFrom(v.getClass())).findAny().orElseThrow();
}
/**
* Collect all statics annotated with @JunitFieldProvider.
*/
@Override
public void beforeAll(ExtensionContext context) throws Exception {
collectFields(null, context.getRequiredTestClass(), ReflectionUtils::isStatic);
}
/**
* Collect any instance fields annotated with @JunitFieldProvider.
*/
@Override
public void beforeEach(ExtensionContext context) {
context.getRequiredTestInstances().getAllInstances() //
.forEach(instance -> collectFields(instance, instance.getClass(), ReflectionUtils::isStatic));
}
private void collectFields(Object testInstance, Class<?> testClass,
Predicate<Field> predicate) {
findAnnotatedFields(testClass, JunitFieldProvider.class, predicate).forEach(field -> {
try {
makeAccessible(field);
myValues.add(field.get(testInstance));
}
catch (Exception t) {
ExceptionUtils.throwAsUncheckedException(t);
}
});
}
}

View File

@ -0,0 +1,18 @@
package ca.uhn.test.junit;
import org.junit.jupiter.api.extension.ExtendWith;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Field annotation to register a value for use in JUnit 5 parameter resolution.
*/
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(JunitFieldParameterProviderExtension.class)
public @interface JunitFieldProvider {
}

View File

@ -0,0 +1,4 @@
/**
* Junit helpers
*/
package ca.uhn.test.junit;

View File

@ -0,0 +1,46 @@
package ca.uhn.test.junit;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class JunitFieldParameterProviderExtensionTest {
/** Verify statics annotated with @{@link JunitFieldProvider} are available to static junit methods. */
@Nested
class AnnotationRegistration implements IParameterizedTestTemplate {
@JunitFieldProvider
static final CaseSupplyingFixture ourFixture = new CaseSupplyingFixture();
}
/** Verify explicit registration of the extension.*/
@Nested
class ExplicitRegistration implements IParameterizedTestTemplate {
static final CaseSupplyingFixture ourFixture = new CaseSupplyingFixture();
@RegisterExtension
static final JunitFieldParameterProviderExtension ourExtension = new JunitFieldParameterProviderExtension(ourFixture);
}
static class CaseSupplyingFixture {
public List<Integer> getCases() {
return List.of(1,2,3);
}
}
interface IParameterizedTestTemplate {
@ParameterizedTest
// intellij complains when a static source requires params
@SuppressWarnings("JUnitMalformedDeclaration")
@MethodSource("testCaseSource")
default void testStaticFactoryBound(int theTestCase) {
// given
assertTrue(theTestCase > 0);
}
static List<Integer> testCaseSource(CaseSupplyingFixture theFixture) {
return theFixture.getCases();
}
}
}