Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parameterizing with array in Junit 5 (or other testing Java library) in smarter fashion

Tags:

I'm trying to parameterize this test:

@Test
public void reverseQuote(double[] qsp) throws Exception {
...}

It seems absurd to me that it doesn't exists some quick method to initialize array qsp like, for example, ValueSource:

@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void testWithValueSource(int argument) {
    assertNotNull(argument);
}

my aim is to do something like @ValueSource(doublesArray = {new double[]{1.0, 2.0, 3.0}}) (that now returns error). Doesn't exists anything that permits something similar?? Other answers seem to suggest only elaborated ways, like using @MethodSource or @ConvertWith.

I accept answers implementing other testing libraries, too.

like image 317
Lore Avatar asked Feb 14 '18 14:02

Lore


People also ask

Which of the following annotation is used in junit5?

JUnit 5 provides a different approach. It provides the @BeforeAll annotation which is used on a static function, to work with static members of the class.

What is Parameterized test in JUnit?

JUnit 4 has introduced a new feature called parameterized tests. Parameterized tests allow a developer to run the same test over and over again using different values. There are five steps that you need to follow to create a parameterized test. Annotate test class with @RunWith(Parameterized. class).


2 Answers

Ok, this is gonna be a weird answer, but it works and it was kinda fun to do.

First thing: your way is impossible. Not because of JUnit or any related API, but because of Java - valid annotation type elements (annotation arguments can only be primitive, String, Class, Enum, other annotation and array of all those).

Second thing: we can get around the first one. Check this:

@ArraySources(
  arrays = {
    @ArraySource(array = {1, 2, 3}),
    @ArraySource(array = {4, 5, 6}),
    @ArraySource(array = {7, 8, 9})
  }
)

As it says, annotation can have other annotations as arguments, and arrays of those, so we are using those 2 rules here.

Third thing: how does that help? We can add our own annotation + argument provider, JUnit 5 is expansible in that way.

Both annotations:

@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@ArgumentsSource(ArrayArgumentsProvider.class)
public @interface ArraySources {
    ArraySource[] arrays();
}

@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ArraySource {
    int[] array() default {};
}

Argument provider based on the annotations:

public class ArrayArgumentsProvider implements ArgumentsProvider, AnnotationConsumer<ArraySources> {
    private List<int[]> arguments;

    public void accept(ArraySources source) {
        List<ArraySource> arrays = Arrays.asList(source.arrays());

        this.arguments = arrays.stream().map(ArraySource::array).collect(Collectors.toList());
    }

    public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
        return this.arguments.stream().map(Arguments::of);
    }
}

And the final test using those:

public class ArraySourcesTest {
    @ParameterizedTest
    @ArraySources(
            arrays = {
                    @ArraySource(array = {1, 2, 3}),
                    @ArraySource(array = {4, 5, 6}),
                    @ArraySource(array = {7, 8, 9})
            }
    )
    void example(int[] array) {
        System.out.println(Arrays.toString(array));
        System.out.println("Test Over");
    }
}

/* Output
[1, 2, 3]
Test Over
[4, 5, 6]
Test Over
[7, 8, 9]
Test Over
*/

You mentioned @MethodSource as complicated, well, so I think I failed in this matter, but it works. It could be simplified and enhanced obviously (like naming annotation arguments as defaults - value - and I only did it for int to show the idea). Not sure if you could achieve the same with existing features (ArgumentsProvider and ArgumentSources), but this looks more specific (you know you are working with arrays) and shows possibilities of extending JUnit5, may be useful in other case.

like image 139
Shadov Avatar answered Sep 19 '22 17:09

Shadov


Using a combination of Junit Parameterized Tests and YAML Parsing might be something to consider.

@RunWith(Parameterized.class)
public class AnotherParameterizedTest {

    private final HashMap row;

    @Parameterized.Parameters(name="Reverse Lists Tests # {index}:")
    public static List<Map<String, Object>> data() {
        final TestData testData = new TestData(""+
             "|   ID   |       List         |  Expected   |                \n"+
             "|   0    |    [1, 2, 3]       |  [3, 2, 1]  |                \n"+
             "|   1    |    [2, 3, 5]       |  [3, 2, 1]  |                \n"+
             "|   2    |    [5, 6, 7]       |  [ 7, 6, 5] |                \n"
        );
        // parsing each row using simple YAML parser and create map per row
        return testData.getDataTable();
    }

    // Each row from the stringified table above will be 
    // split into key=value pairs where the value are parsed using a 
    // yaml parser. this way, values can be pretty much any yaml type
    // like a list of integers in this case. 
    public AnotherParameterizedTest(HashMap obj) {
        this.row = obj;
    }

    @Test
    public void test() throws Exception {
        List orgListReversed = new ArrayList((List) row.get("List"));
        Collections.reverse(orgListReversed);
        assertEquals((List) row.get("Expected"), orgListReversed);
    }

}

Instead of using a String I am using a Excel Reader to do the same with simple Excel Tables. Parsing each row into one Map using YAML for the Values.

Junit IDE Test Results

The same just tested using Junit Jupiter gives nicer results in the IDE Runner.

import static org.junit.jupiter.api.Assertions.assertEquals;

import de.deicon.yatf.runner.dsl.TestData;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.Collections;
import java.util.List;
import java.util.Map;

public class FirstTest {

    @ParameterizedTest
    @MethodSource("testTable")
    public void test(Map row){
        List reversedList = (List) row.get("List");
        Collections.reverse(reversedList);
        assertEquals((List)row.get("Expected"), reversedList);
    }

    static List<Map<String, Object>> testTable() {
        return new TestData(""+
                "|ID|   List                  |Expected               |         \n"+
                "|0 | [1,2,3]                 | [3,2,1]               |         \n"+
                "|1 | [hans, peter, klaus]    | [klaus, peter, hans]  |         \n"
        ).getDataTable();
    }

}

enter image description here

like image 45
Dieter Avatar answered Sep 16 '22 17:09

Dieter