Reputation: 1524
In my test class, I have several parameterized methods where parameters are a part of object initialization. I use the same parameters for all of them, and then I test methods which depend on one or more of the parameters.
@MethodSource(value = "validSizes")
@ParameterizedTest
void testGetHeight(int terrainWidth, int height) {
World world = new World(terrainWidth, height);
assertEquals(height, world.getHeight());
}
This may lead to code duplication - I need to repeat parameters, World
construction and @MethodSource
annotation.
I would like to avoid code duplication because if I add another parameter to the constructor, I would have to change all of the testing methods which depend on the parameters.
Can I do it with @BeforeEach
annotation, i.e. parametrize the setUp()
method, or do it another way? Is it a good practice to test such methods with different parameters?
Upvotes: 3
Views: 1513
Reputation: 315
You could write your own Argument Provider which provides a fully constructed world object and injects it into your setup/BeforeEach method (where you store it for usage in your test methods). That way you minimize your setup code and condensate your initialization logic to one specific place in your code.
Edit: I had a wrong mechanism in mind. SO here is the right example and its clarification what I meant:
Lets assume your world object looks something like this:
class World {
private final int width;
private final int height;
public World(int width, int height) {
this.width = width;
this.height = height;
}
@Override
public String toString() {
return "World{" + "width=" + width + ", height=" + height + '}';
}
}
And you have several Test methods where you need a set of different World objects (possibly dynamically created), something like this:
class ArgumentProviderTest {
@ParameterizedTest
@ArgumentsSource(WorldArgumentProvider.class)
void yourFirstTest(World w) {
System.out.println(w);
}
@ParameterizedTest
@ArgumentsSource(WorldArgumentProvider.class)
void yourSecondTest(World w) {
System.out.println(w);
}
}
Here you see, the tests all expect a World object as Argument. Now we need a way to provide world objects. For this we write our own ArgumentsProvider which is also straight-forward:
class WorldArgumentProvider implements ArgumentsProvider {
private Collection<World> createWorlds() {
// Somehow create a bunch of World objects
// !!! Here goes your initialization logic. !!!
return ThreadLocalRandom.current()
.ints()
.limit(10)
.mapToObj(i -> new World(i % 100, i / 100))
.collect(Collectors.toList());
}
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
// create an Arguments object from these (discrete) worlds.
// remember: one method takes one Arguments object which carries each single argument/parameter
return createWorlds().stream().map(Arguments::of);
}
}
And here you can do whatever you want with full Compiler support (instead of using method name as strings).
Maybe you could achieve a similar behaviour using DynamicTests with a @TestFactory, but that depends on your exact tests.
My first thought was to use a ParameterResolver which basically works like this:
However, with this mechanism you can not tell the Jupiter engine to construct multiple Test instances for a method (Jupiter introduced the concept of TestTemplate for such a thing). Interestingly, this Parameterized-Class conect was implemented in jUnit-4 (as the only way of parameterized tests) and is AFAIK a feature hopefully coming in the future in Jupiter. I am looking forward to it.
Upvotes: 2
Reputation: 1524
You could use a wrapper class to have all initialization code in one place.
class WorldWrapper {
public final World world;
public final int terrainWidth;
public final int height;
//constructor to initialize the fields
}
You still would need to annotate each method with @MethodSource
and repeat a single parameter but wouldn't have to worry if you decide to change constructor parameters.
@MethodSource(value = "validSizes")
@ParameterizedTest
void testGetHeight(WorldWrapper param) {
assertEquals(param.height, param.world.getHeight());
}
Upvotes: 1