The JSON argument sources let you provide arguments for parameterized tests from JSON. There are three annotations:

  • @JsonSource for lenient inline JSON, works with regular string literals and text blocks

  • @JsonFileSource for JSON files from the local file system

  • @JsonClasspathSource for JSON files from the classpath

There are various ways how the method arguments for a single parametrized test are provided. By default, the root of the source will be treated as candidate for the test arguments. If the root is an object then the entire object will be one argument, if the root is an array then every element of the array will be one argument.

It is also possible to use a nested array from the provided JSON to access the source for the test arguments. The JsonFileSource#data can be used to tell the extraction mechanism to use the element with that name to look for the source of the data.

Depending on the test method parameters, the extraction of the values might differ.

Method Arguments

Single Argument Methods

If the method has a single argument, the JSON object argument will be converted to that type.

Argument type
public class Jedi {

    public String name;
    public String height;

    @Override
    public String toString() {
        return "Jedi {" + "name='" + name + '\'' + ", height=" + height + '}';
    }

}
JSON Source File
[
  {
    "name": "Luke",
    "height": 172
  },
  {
    "name": "Yoda",
    "height": 66
  }
]
@ParameterizedTest
@JsonClasspathSource("jedis.json")
void singleJedi(Jedi jedi) {
    // YOUR TEST CODE HERE
}
@ParameterizedTest
@JsonSource("["
        + "  { name: 'Luke', height: 172  },"
        + "  { name: 'Yoda', height: 66 }"
        + "]")
void singleJedi(Jedi jedi) {
    // YOUR TEST CODE HERE
}

This parametrized test will generate the following test executions:

  • [1] Jedi {name='Luke', height=172}

  • [2] Jedi {name='Yoda', height=66}

It is also possible to extract only a single element from each argument object by using the @Property annotation.

@ParameterizedTest
@JsonClasspathSource("jedis.json")
void singleJediProperty(
        @Property("name") String jediName) {
    // YOUR TEST CODE HERE
}
@ParameterizedTest
@JsonSource({
        "{ name: 'Luke', height: 172  }",
        "{ name: 'Yoda', height: 66 }"
})
void singleJediProperty(
        @Property("name") String jediName) {
    // YOUR TEST CODE HERE
}

This parametrized test will generate the following tests:

  • [1] Luke

  • [2] Yoda

The extension will automatically map JSON types to their corresponding Java types, including arrays:

@ParameterizedTest
@JsonSource({
        "{ name: 'Yoda', padawans: ['Dooku', 'Luke']  }",
        "{ name: 'Obi-Wan', padawans: ['Anakin', 'Luke'] }"
})
void multipleJedis(
        @Property("padawans") List<String> padawanNames) {
    // YOUR TEST CODE HERE
}

Multiple Argument Methods

If the method has multiple arguments, each JSON object argument will be deconstructed to each of the method arguments. By default, the method argument name will be used for locating the element that needs to be taken from the JSON object. You can also use @Property to give the name of the element that needs to be extracted.

Important

If your test sources are not compiled using the --parameters flag then the names of the arguments will not be like they are written in the source code. In that the situation you need to use @Property instead.

Using the same jedis.json and the following test

@ParameterizedTest
@JsonClasspathSource("jedis.json")
void deconstructFromArray(
        @Property("name") String name,
        @Property("height") int height) {
    // YOUR TEST CODE HERE
}
@ParameterizedTest
@JsonSource({
        "{ name: 'Yoda', height: 66 }",
        "{ name: 'Luke', height: 172 }",
})
void deconstructFromArray(
        @Property("name") String name,
        @Property("height") int height) {
    // YOUR TEST CODE HERE
}
// @formatter:on

This parametrized test will generate the following tests:

  • [1] Luke, 172

  • [2] Yoda, 66

Extracting nested array

Sometimes we want to extract a nested array instead of the root element. For this purpose JsonClasspathSource#data can be used.

Jedi with nested array
{
  "name": "Luke",
  "height": 172,
  "vehicles": [
    {
      "name": "Snowspeeder",
      "length": 4.5
    },
    {
      "name": "Imperial Speeder Bike",
      "length": 3
    }
  ]
}

Here we want to test the vehicles. The test for this will look like:

@ParameterizedTest
@JsonClasspathSource(
        value = "luke.json", data = "vehicles")
void lukeVehicles(
        @Property("name") String name,
        @Property("length") double length) {
    // YOUR TEST CODE HERE
}

This parametrized test will generate the following tests:

  • [1] Snowspeeder, 4.5

  • [2] Imperial Speeder Bike, 3

JSON Parser Integration

To not make users' dependency management more complex, JUnit Pioneer has no run-time dependencies. At the same time, it’s not parsing JSON itself and relies on third-party libraries for that. For this extension that means that projects who want to use it need to pull in a JSON parser themselves. This is the list of supported parsers:

If you need support for another parser, please open an issue. If your project does not already depend on a supported JSON parser, you can add it as follows.

Gradle

Gradle offers two ways to pull in a parser. The recommended one is to use feature variants:

testRuntimeOnly("org.junit-pioneer:junit-pioneer") {
    capabilities {
        requireCapability("org.junit-pioneer:junit-pioneer-jackson")
    }
}

Alternatively, the dependency can be added directly:

testImplementation("com.fasterxml.jackson.core:jackson-databind:$CURRENT_VERSION")

Maven

In Maven, add the parser as a test dependency:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>$CURRENT_VERSION</version>
    <scope>test</scope>
</dependency>

Custom ObjectMapper for Jackson

By default, Jackson does not support complex fields (e.g.: LocalDate). It uses an extension/module system for additional capabilities. You have to register these modules on the ObjectMapper instance you want to use.

Pioneer provides the service interface ObjectMapperProvider for you to be able to provide your own ObjectMapper instance (and register modules on it). This interface has three methods:

  • get() for supplying an ObjectMapper.

  • getLenient() this is a default method that copies the value from get and enables some convenience features. The ObjectMapper provided by this method is the one Pioneer uses to parse your JSON. If you use your own custom ObjectMapper implementation, you might have to override this method.

  • id() to identify the ObjectMapperProvider. This has to be a unique String. The ObjectMapperProvider used by Pioneer has the id "default".

Pioneer uses ServiceLoader to load in your implementation of ObjectMapperProvider. You can tell Pioneer to use your implementation in one of two ways.

  • By using @UseObjectMapper on your test. This will make that single test use the ObjectMapper provided by the ObjectMapperProvider with the id specified. You can also add @UseObjectMapper to your own annotation as meta-annotation.

Annotating your test
@ParameterizedTest
@UseObjectMapper("custom")
@JsonClasspathSource("jedis.json")
void singleJediProperty(@Property("name") String jediName) {
    // YOUR TEST CODE HERE
}
Creating your own annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ParameterizedTest
@UseObjectMapper("custom")
public @interface JsonTest {
}
  • By supplying the id of an ObjectMapperProvider implementation as a configuration parameter. The configuration parameter is org.junitpioneer.jupiter.json.objectmapper. All tests will use the ObjectMapper provided by the ObjectMapperProvider with the id specified.

Configuration parameter example
org.junitpioneer.jupiter.json.objectmapper=custom

If both the configuration parameter and the @UseObjectMapper annotation is present, the annotation value will be used.

Java Modules

If your test code runs as a module, the JSON parser must make it into the module graph. That means (1) it must be on the module path and (2) it must be resolved.

The steps above ensure that your build tool knows about the parser and should accomplish (1), but if no other module depends on the parser (directly or indirectly), (2) requires additional work. In that case, you need to manually resolve the module by applying the command line option --add-modules=com.fasterxml.jackson.databind to the Java process that executes the tests.

Thread-Safety

This extension is safe to use during parallel test execution.