Use Java Annotation With JUnit 5 Extension

Java annotations are quite useful in many areas such as introspection, Aspect-Oriented Programming, etc. Spring Framework uses quite a lot of annotations. My team has been using annotations extensively in our projects and one particular use case is using annotations together with JUnit 5 ParameterResolver to inject test resources.

We will start by creating a custom annotation:

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FileResource {
  /**
   * @return the path to the resource.
   */
  String value();

  /**
   * @return the type of value that should be decoded from the resource
   */
  Class<?> type() default String.class;
}

Then we would need to implement a custom ParameterResolver using the annotation.

public class FileResourceParameterResolver implements ParameterResolver {

  private static final ObjectMapper MAPPER = new ObjectMapper();

  @Override
  public boolean supportsParameter(ParameterContext parameterContext, 
      ExtensionContext extensionContext) throws ParameterResolutionException {
    FileResource fileResource = parameterContext
        .findAnnotation(FileResource.class).orElse(null);
    return fileResource != null && 
        fileResource.type().isAssignableFrom(
        parameterContext.getParameter().getType());
  }

  @SneakyThrows
  @Override
  public Object resolveParameter(ParameterContext parameterContext, 
      ExtensionContext extensionContext) throws ParameterResolutionException {
    var fileResource = parameterContext
        .findAnnotation(FileResource.class)
        .orElseThrow(IllegalArgumentException::new);
    String path = fileResource.value();
    InputStream content = getClass().getClassLoader().getResourceAsStream(path);
    if (content == null) {
      throw new ParameterResolutionException("Resource not found: " + path);
    }
    if (fileResource.type() == String.class) {
      return IOUtils.toString(content, StandardCharsets.UTF_8);
    }
    return MAPPER.readValue(content, parameterContext.getParameter().getType());
  }
}

Now we have our custom ParameterResolver ready to use. In our test, what we need to do is the following:

@ExtendWith({ FileResourceParameterResolver.class })
class SomeTest {

  @Test
  void importResourceTest(@FileResource("some/json/file.json") String inputJson) {
    // use content below in test
  }
}