|
package net.twisterrob.java.test.jupiter; |
|
|
|
import java.lang.annotation.*; |
|
import java.lang.reflect.*; |
|
import java.util.*; |
|
|
|
import javax.annotation.Nullable; |
|
|
|
import org.junit.jupiter.api.extension.*; |
|
import org.junit.jupiter.api.extension.ExtensionContext.Namespace; |
|
|
|
import com.flextrade.jfixture.*; |
|
import com.flextrade.jfixture.annotations.Fixture; |
|
|
|
/** |
|
* Extension for {@code {@link com.flextrade.jfixture.JFixture} based on {@link MockitoExtension}. |
|
* Use the extension APIs of JUnit 5 to provide automatic creation of fixtures where needed. |
|
* |
|
* The {@link Fixt } annotation is created to bridge the gap |
|
* while jfixture#32 is implemented and released. |
|
* |
|
* @see <a href="https://github.com/FlexTradeUKLtd/jfixture/issues/32">jfixture#32</a> |
|
*/ |
|
public class JFixtureExtension implements TestInstancePostProcessor, ParameterResolver { |
|
|
|
private static final Namespace NAMESPACE = Namespace.create(JFixtureExtension.class); |
|
private static final Object GLOBAL_FIXTURE_KEY = JFixture.class; |
|
|
|
@Override |
|
public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws IllegalAccessException { |
|
JFixture fixture = null; |
|
|
|
Field fixtureField = new FieldHunter(testInstance).find(); |
|
if (fixtureField != null) { |
|
fixtureField.setAccessible(true); |
|
// get the value from the field, if any |
|
fixture = (JFixture)fixtureField.get(testInstance); |
|
} |
|
if (fixture == null) { |
|
// none found, create one |
|
fixture = new JFixture(); |
|
} |
|
if (fixtureField != null) { |
|
// inject JFixture into the test, may re-set the same reference |
|
fixtureField.set(testInstance, fixture); |
|
} |
|
context.getStore(NAMESPACE).put(GLOBAL_FIXTURE_KEY, fixture); |
|
FixtureAnnotations.initFixtures(testInstance, fixture); |
|
} |
|
|
|
@Override |
|
public boolean supports(ParameterContext parameterContext, ExtensionContext extensionContext) { |
|
return parameterContext.getParameter().isAnnotationPresent(Fixture.class) |
|
|| parameterContext.getParameter().isAnnotationPresent(Fixt.class) |
|
|| isJFixture(parameterContext) |
|
; |
|
} |
|
|
|
private boolean isJFixture(ParameterContext parameterContext) { |
|
return JFixture.class.isAssignableFrom(parameterContext.getParameter().getType()); |
|
} |
|
|
|
@Override |
|
public Object resolve(ParameterContext parameterContext, ExtensionContext extensionContext) { |
|
if (isJFixture(parameterContext)) { |
|
return getJFixture(extensionContext); |
|
} |
|
return getFixture(parameterContext.getParameter(), extensionContext); |
|
} |
|
|
|
private static JFixture getJFixture(ExtensionContext extensionContext) { |
|
return (JFixture)extensionContext.getStore(NAMESPACE).get(GLOBAL_FIXTURE_KEY); |
|
} |
|
|
|
private Object getFixture(Parameter parameter, ExtensionContext extensionContext) { |
|
JFixture fixture = getJFixture(extensionContext); |
|
Type fixtType = parameter.getParameterizedType(); |
|
return fixture.create(fixtType); |
|
} |
|
|
|
/** |
|
* Placeholder annotation until {@link Fixture} is changed to be able to place it on parameters. |
|
*/ |
|
@Target(ElementType.PARAMETER) |
|
@Retention(RetentionPolicy.RUNTIME) |
|
public @interface Fixt { |
|
|
|
} |
|
|
|
// TODO support @Nested |
|
private static class FieldHunter { |
|
|
|
private final Object testInstance; |
|
|
|
public FieldHunter(Object testInstance) { |
|
this.testInstance = testInstance; |
|
} |
|
|
|
/** |
|
* @return the field that contains or needs to contain the JFixture object; or null if none found |
|
* @throws IllegalStateException if multiple matching fields found |
|
*/ |
|
public @Nullable Field find() throws IllegalStateException { |
|
List<Field> selfFields = findJFixtureFields(testInstance.getClass()); |
|
return selectJFixtureField(selfFields); |
|
} |
|
|
|
private Field selectJFixtureField(List<Field> fields) { |
|
if (fields.isEmpty()) { |
|
return null; |
|
} |
|
if (fields.size() == 1) { |
|
return fields.get(0); |
|
} |
|
Field found = null; |
|
for (Field field : fields) { |
|
if (found != null) { |
|
if (!isSelected(found) && isSelected(field)) { |
|
// override found by the one being investigated, which is better because it is annotated |
|
found = field; |
|
} else if (isSelected(found) == isSelected(field)) { |
|
throw new IllegalStateException("Too many JFixture fields found," |
|
+ " annotate one of the with @Fixture to select for shared usage."); |
|
} |
|
} else { |
|
found = field; |
|
} |
|
} |
|
return found; |
|
} |
|
|
|
private boolean isSelected(Field field) { |
|
return field.getAnnotation(Fixture.class) != null; |
|
} |
|
|
|
private List<Field> findJFixtureFields(Class<?> testClass) { |
|
List<Field> fields = new ArrayList<>(); |
|
for (Class<?> clazz = testClass; clazz != null; clazz = clazz.getSuperclass()) { |
|
for (Field field : clazz.getDeclaredFields()) { |
|
if (JFixture.class.isAssignableFrom(field.getType())) { |
|
fields.add(field); |
|
} |
|
} |
|
} |
|
return fields; |
|
} |
|
} |
|
} |