Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@TWiStErRob
Last active February 11, 2017 22:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TWiStErRob/8d9abea2777c79d01513e896b3cb5ebb to your computer and use it in GitHub Desktop.
Save TWiStErRob/8d9abea2777c79d01513e896b3cb5ebb to your computer and use it in GitHub Desktop.
JUnit Jupiter extension for creating JFixture objects into tests

Not demonstrated here but it's also possible to @Fixture annotated fields, because the extension calls initFixtures.

One can also create a JFixture fixture field, which will be either used or filled, depending whether it had a value or not.

There's an outstanding issue where the .customisation() only affects method-arg injected values, but not field-injected ones. I'm not sure how to work around this, because there doesn't seem to be an API to work around this. An option I see would be to expect an optional void customize(JFixture fixture); to be present where all customizations can be made before the extension calls initFixtures.

public class Example {
public static <T1, T2> Predicate<Pair<T1, T2>> expand(BiPredicate<? super T1, ? super T2> func) {
return objects -> func.test(objects.getValue0(), objects.getValue1());
}
}
@ExtendWith(MockitoExtension.class)
@ExtendWith(JFixtureExtension.class)
class ExampleTest {
@Test void testExpandDelegates(
@Mock BiPredicate<C1, C2> pred,
@Fixt C1 input1, @Fixt C2 input2, @Fixt boolean output)
throws Exception {
when(pred.test(any(), any())).thenReturn(output);
Example.expand(pred).test(Pair.with(input1, input2));
verify(pred).test(input1, input2);
}
// Test classes for use with JFixture (accessible ctor required)
private static class C1 { C1() {} }
private static class C2 { C2() {} }
}
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;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment