JUnit 5 extensions: Field injection

Marcus • May 21, 2020

Aerial view of green rice fields.

Now that we looked at how parameter resolution works for general lifecycle methods last time, let’s look at the alternative.

While JUnit 4 did support supplying parameters for constructors, you could also simply declare fields as @Parameter. Let’s see how we can make this available in our JUnit 5 parameter resolver.

Extending on Extensions

The class we created last time is an Extension: One of the many ways through which JUnit 5 is extensible. All that’s left on the part of developer for using it is annotating your test code with @RegisterExtension(...).

There are multiple ways through which we could provide values for fields annotated with our custom annotation, and I’ll discuss feasible extension points below.

But first, let’s get some preparation done and change our @ResolvedParameter annotation slightly to support placing it on fields.

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.FIELD})
public @interface ResolvedParameter {
  /** The position of the parameter. */
  int value();
}

And we’ll use the following simple test definition to check if any of our approaches actually works:

class MyTest {
  @ResolvedParameter(0)
  int fieldValue;

  @ParameterizedTest
  @ValueSource(ints = {7, 8})
  void parametersWork(int parameterValue) {
    assertEquals(parameterValue, fieldValue);
    assertNotEquals(0, parameterValue);
    assertNotEquals(0, fieldValue);
  }
}

BeforeTestExecutionCallback & BeforeEachCallback

Implementing either of these two callbacks is a straightforward case, actually. And with a little bit of consideration for what we intend to do, the code is the same for both: Get the parameters from the current execution context, and set them using reflection:

public class BeforeEachResolver
    extends SimpleParameterResolver
    implements BeforeEachCallback {

  @Override
  public void beforeEach(ExtensionContext context)
      throws IllegalAccessException {
    Object testInstance = context.getRequiredTestInstance();

    Object\[\] availableParameters = resolveArguments(context);
    List<Field> fieldsToInject =
      AnnotationSupport.findAnnotatedFields(
        context.getRequiredTestClass(), ResolvedParameter.class);

    for (Field field : fieldsToInject) {
      ResolvedParameter desiredParameter =
        field.getAnnotation(ResolvedParameter.class);
      field.set(testInstance,
        availableParameters\[desiredParameter.value()\]);
    }
  }
}

To apply the same in BeforeTestExecutionCallback, simply implement that interface and change the method name beforeEach to beforeTestExecution.

The base class, SimpleParameterResolver, was explained in part 1, and by extending it here we allow for parameters to be resolved either as fields or method parameters.

With the solid foundation[1] we set up previously, this step was fairly simple. The source code is, as with the previous part, available:

You can view the full source code on GitHub.

Up next

In part 3, we’ll take another look into getting things injected into our test context: We’re presumably done with parameters after this part, but there are more options left to explore for resources.

Part 3 ->


  1. Reflection typically isn’t what you would describe as a solid foundation in any case, and the code has only been confirmed working as of JUnit 5.6.0.
}