15

I have a test in which I want to pass three parameters:

  1. String
  2. Enum
  3. Array of Strings

Example:

@ParameterizedTest
    @CsvSource({
            "/path/to/first/file.xlsx, FIRST, {THIRD PARAMETER SHOULD BE ARRAY OF STRINGS}",
            "/path/to/second/file.xlsx, SECOND, {THIRD PARAMETER SHOULD BE ARRAY OF STRINGS}"})
    void uploadFile(String path, FileType type, String[] errors) {
        HttpEntity httpEntity = prepareFileUploadEntity(path, type);

        ResponseEntity<ArrayList> response = getRestTemplate(AppRole.USER).exchange(UPLOAD_URL, HttpMethod.POST, httpEntity, ArrayList.class);

        assertNotNull(response);
        assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
        assertEquals(errors.length, response.getBody().size());
        for (String error : errors) {
            assertTrue(response.getBody().contains(error));
        }
    }

How can I pass the third parameter as an array of strings, cause now I have the error that third parameter can`t be resolved:

org.junit.jupiter.api.extension.ParameterResolutionException: Error resolving parameter at index 2

4 Answers 4

24

@CsvSource uses implicit conversion to convert CSV values to primitives, Enums or Dates. For other types like Arrays, you need explicit conversion.

Assuming you have a CSV annotation in a format like @CsvSource("abc, 123, 'foo, bar'"), you can implement an argument converter like this to treat the last CSV column as an array:

import org.junit.jupiter.params.converter.ArgumentConversionException;
import org.junit.jupiter.params.converter.SimpleArgumentConverter;

public class StringArrayConverter extends SimpleArgumentConverter {

    @Override
    protected Object convert(Object source, Class<?> targetType) throws ArgumentConversionException {
        if (source instanceof String && String[].class.isAssignableFrom(targetType)) {
            return ((String) source).split("\\s*,\\s*");
        } else {
            throw new IllegalArgumentException("Conversion from " + source.getClass() + " to "
                                               + targetType + " not supported.");
        }
    }

}

Then you can use that converter on the third argument:

@ParameterizedTest
@CsvSource("abc, 123, 'foo, bar'")
void test(String column1, int column2, @ConvertWith(StringArrayConverter.class) String[] column3) {
    assertEquals(column1, "abc");
    assertEquals(column2, 123);
    assertEquals(column3[0], "foo");
    assertEquals(column3[1], "bar");
}
Sign up to request clarification or add additional context in comments.

Comments

4

Small correction, in

return ((String) source).split("\\s*,\\s*");

should be a different sign (e.g. ';') instead of ','

Then in test should be

@ParameterizedTest
@CsvSource("abc, 123, foo; bar")

Final version which works for me:

import org.junit.jupiter.params.converter.ArgumentConversionException;
import org.junit.jupiter.params.converter.SimpleArgumentConverter;

public class StringArrayConverter extends SimpleArgumentConverter {

    @Override
    protected Object convert(Object source, Class<?> targetType) throws ArgumentConversionException {
        if (source instanceof String && String[].class.isAssignableFrom(targetType)) {
            return ((String) source).split("\\s*;\\s*");
        } else {
            throw new IllegalArgumentException("Conversion from " + source.getClass() + " to "
                                               + targetType + " not supported.");
        }
    }

}

Test:

@ParameterizedTest
@CsvSource("abc, 123, foo; bar")
void test(String column1, int column2, @ConvertWith(StringArrayConverter.class) String[] column3) {
    assertEquals(column1, "abc");
    assertEquals(column2, 123);
    assertEquals(column3[0], "foo");
    assertEquals(column3[1], "bar");
}

1 Comment

"correction" not needed. In my opinion it is best to indicate the "array" as in Kapep answer inside a single quoted string
1

You can easily do something like this:

@DisplayName("Should rotate successfully")
@ParameterizedTest()
@CsvSource({
        "'[0, 0, 0]', 5, '[0, 0, 0]', false",
})
void givenAnArrayAndAShiftSize_ShouldSuccesfullyRotateOrReturnFalse(String arrayStr, int shiftSize,
        String expectedArrayStr, boolean expecetedRotated) {

    var window = Arrays.asList(arrayStr.replace("[", "").replace("]", "").split(","))
            .stream()
            .mapToLong(c -> Long.parseLong(c.toString().trim()))
            .toArray();

    var result = Arrays.asList(expectedArrayStr.replace("[", "").replace("]", "").split(","))
            .stream()
            .mapToLong(c -> Long.parseLong(c.toString().trim()))
            .toArray();

    var rotated = Utils.shiftIfSizeIsValid(window, shiftSize);

    assertEquals(expecetedRotated, rotated);

    for (int i = 0; i < window.length; i++) {
        assertEquals(result[i], window[i]);
    }
}

It is simpler and more readable.

Comments

0

With a little ingenuity, we can convert to almost any iterable with full generic support, including primitive and wrapper type arrays.

The following implementation is from my open source library jdata.

I let jackson-databind do all the heavy lifting. It has the added advantage that I get to use the familiar notation of JSON list in my tests instead of some homegrown weirdity.

Note that the following code uses constructs from Java 17, such as Text Blocks and Pattern Matching for instanceof.

Converter:

package junit;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.params.converter.ArgumentConversionException;
import org.junit.jupiter.params.converter.SimpleArgumentConverter;

public class IterableConverter extends SimpleArgumentConverter {
  private static final ObjectMapper MAPPER = new ObjectMapper();

  @Override
  protected Object convert(Object source, Class<?> targetType) throws ArgumentConversionException {
    if (source instanceof String s) {
      try {
        return MAPPER.readValue(s, targetType);
      } catch (JsonProcessingException jpe) {
        throw new ArgumentConversionException(jpe.getMessage(), jpe);
      }
    } else {
      throw new ArgumentConversionException("Can only convert from String");
    }
  }
}

Test:

import junit.IterableConverter;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.converter.ConvertWith;
import org.junit.jupiter.params.provider.CsvSource;

class MyTests {
  @ParameterizedTest
  @CsvSource(
      delimiter = '|',
      textBlock =
          """
    [5,7,7,8,8,10] | 8 | [3, 4]
    [5,7,7,8,8,10] | 6 | [-1,-1]
    [] | 0 | [-1,-1]
    [1] | 0 | [-1,-1]
      """)
  public void testStuff(
      @ConvertWith(IterableConverter.class) int[] nums,
      int target,
      @ConvertWith(IterableConverter.class) int[] expected) {
    // test code;
  }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.