Skip to content

Commit

Permalink
Make quote character in @CsvFileSource configurable
Browse files Browse the repository at this point in the history
Prior to this commit, the quote character for quoted strings in
@CsvFileSource was hard coded to a double quote (") and could not be
changed.

Commit f1cbfbe introduced a new quoteCharacter attribute in
@CsvSource that allows the user to change the quote character.

For consistency between the two features, this commit introduces a new
quoteCharacter attribute in @CsvFileSource. The quoteCharacter defaults
to a double quote for backward compatibility.

Closes junit-team#2735
  • Loading branch information
sbrannen authored and runningcode committed Feb 15, 2023
1 parent 5b1bc24 commit 8c74569
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[[release-notes-5.8.2]]
== 5.8.2

*Date of Release:* October ❓, 2021
*Date of Release:* November ❓, 2021

*Scope:*

* Text blocks in `@CsvSource` are treated as CSV files
* Custom quote character support in `@CsvSource`
* Text blocks in `@CsvSource` are treated like CSV files
* Custom quote character support in `@CsvSource` and `@CsvFileSource`

For a complete list of all _closed_ issues and pull requests for this release, consult the
link:{junit5-repo}+/milestone/60?closed=1+[5.8.2] milestone page in the JUnit repository on
Expand All @@ -24,14 +24,13 @@ No changes.

==== New Features and Improvements

* Text blocks in `@CsvSource` are now treated as complete CSV files, including support for
comments beginning with a `+++#+++` symbol as well as support for new lines within
* Text blocks in `@CsvSource` are now treated like complete CSV files, including support
for comments beginning with a `+++#+++` symbol as well as support for new lines within
quoted strings. See the
<<../user-guide/index.adoc#writing-tests-parameterized-tests-sources-CsvSource, User
Guide>> for details and examples.
* The quote character for _quoted strings_ in `@CsvSource` is now configurable via the new
`quoteCharacter` attribute, which defaults to a single quote (`'`) for backward
compatibility.
* The quote character for _quoted strings_ in `@CsvSource` and `@CsvFileSource` is now
configurable via new `quoteCharacter` attributes in each annotation.


[[release-notes-5.8.2-junit-vintage]]
Expand Down
15 changes: 8 additions & 7 deletions documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1344,9 +1344,9 @@ The default delimiter is a comma (`,`), but you can use another character by set
`String` delimiter instead of a single character. However, both delimiter attributes
cannot be set simultaneously.

By default, `@CsvSource` uses a single quote `'` as its quote character, but this can be
By default, `@CsvSource` uses a single quote (`'`) as its quote character, but this can be
changed via the `quoteCharacter` attribute. See the `'lemon, lime'` value in the example
above and in the table below. An empty, quoted value `''` results in an empty `String`
above and in the table below. An empty, quoted value (`''`) results in an empty `String`
unless the `emptyValue` attribute is set; whereas, an entirely _empty_ value is
interpreted as a `null` reference. By specifying one or more `nullValues`, a custom value
can be interpreted as a `null` reference (see the `NIL` example in the table below). An
Expand Down Expand Up @@ -1395,9 +1395,9 @@ In contrast to CSV records supplied via the `value` attribute, a text block can
comments. Any line beginning with a `+++#+++` symbol will be treated as a comment and
ignored. Note, however, that the `+++#+++` symbol must be the first character on the line
without any leading whitespace. It is therefore recommended that the closing text block
delimiter `"""` be placed either at the end of the last line of input or on the following
line, left aligned with the rest of the input (as can be seen in the example below which
demonstrates formatting similar to a table).
delimiter (`"""`) be placed either at the end of the last line of input or on the
following line, left aligned with the rest of the input (as can be seen in the example
below which demonstrates formatting similar to a table).

[source,java,indent=0]
----
Expand Down Expand Up @@ -1458,8 +1458,9 @@ include::{testResourcesDir}/two-column.csv[]
----

In contrast to the default syntax used in `@CsvSource`, `@CsvFileSource` uses a double
quote `"` as the quote character. See the `"United States of America"` value in the
example above. An empty, quoted value `""` results in an empty `String` unless the
quote (`+++"+++`) as the quote character by default, but this can be changed via the
`quoteCharacter` attribute. See the `"United States of America"` value in the example
above. An empty, quoted value (`+++""+++`) results in an empty `String` unless the
`emptyValue` attribute is set; whereas, an entirely _empty_ value is interpreted as a
`null` reference. By specifying one or more `nullValues`, a custom value can be
interpreted as a `null` reference. An `ArgumentConversionException` is thrown if the
Expand Down
2 changes: 1 addition & 1 deletion documentation/src/test/resources/two-column.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Country, reference
Country, Reference
Sweden, 1
Poland, 2
"United States of America", 3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@
* via either {@link #delimiter} or {@link #delimiterString}.
*
* <p>In contrast to the default syntax used in {@code @CsvSource}, {@code @CsvFileSource}
* uses a double quote ({@code "}) as its quote character (see the User Guide for
* examples). An empty, quoted value ({@code ""}) results in an empty {@link String}
* unless the {@link #emptyValue} attribute is set; whereas, an entirely <em>empty</em>
* value is interpreted as a {@code null} reference. By specifying one or more
* {@link #nullValues} a custom value can be interpreted as a {@code null} reference
* (see the User Guide for an example). An
* uses a double quote ({@code "}) as its quote character by default, but this can
* be changed via {@link #quoteCharacter}. An empty, quoted value ({@code ""})
* results in an empty {@link String} unless the {@link #emptyValue} attribute is
* set; whereas, an entirely <em>empty</em> value is interpreted as a {@code null}
* reference. By specifying one or more {@link #nullValues} a custom value can be
* interpreted as a {@code null} reference (see the User Guide for an example). An
* {@link org.junit.jupiter.params.converter.ArgumentConversionException
* ArgumentConversionException} is thrown if the target type of a {@code null}
* reference is a primitive type.
Expand Down Expand Up @@ -95,6 +95,19 @@
*/
String lineSeparator() default "\n";

/**
* The quote character to use for <em>quoted strings</em>.
*
* <p>Defaults to a double quote ({@code "}).
*
* <p>You may change the quote character to anything that makes sense for
* your use case.
*
* @since 5.8.2
*/
@API(status = EXPERIMENTAL, since = "5.8.2")
char quoteCharacter() default '"';

/**
* The column delimiter character to use when reading the CSV files.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ class CsvParserFactory {

private static final String DEFAULT_DELIMITER = ",";
private static final String LINE_SEPARATOR = "\n";
private static final char DOUBLE_QUOTE = '"';
private static final char EMPTY_CHAR = '\0';
private static final boolean COMMENT_PROCESSING_FOR_CSV_FILE_SOURCE = true;

Expand All @@ -37,7 +36,7 @@ static CsvParser createParserFor(CsvSource annotation) {

static CsvParser createParserFor(CsvFileSource annotation) {
String delimiter = selectDelimiter(annotation, annotation.delimiter(), annotation.delimiterString());
return createParser(delimiter, annotation.lineSeparator(), DOUBLE_QUOTE, annotation.emptyValue(),
return createParser(delimiter, annotation.lineSeparator(), annotation.quoteCharacter(), annotation.emptyValue(),
annotation.maxCharsPerColumn(), COMMENT_PROCESSING_FOR_CSV_FILE_SOURCE,
annotation.ignoreLeadingAndTrailingWhitespace());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,19 @@ void providesArgumentsForCarriageReturnAndSemicolon() {
assertThat(arguments).containsExactly(array("foo", "bar"), array("baz", "qux"));
}

@Test
void providesArgumentsWithCustomQuoteCharacter() {
var annotation = csvFileSource()//
.resources("test.csv")//
.quoteCharacter('\'')//
.build();

var arguments = provideArguments(annotation, "foo, 'bar \"and\" baz', qux \n 'lemon lime', banana, apple");

assertThat(arguments).containsExactly(array("foo", "bar \"and\" baz", "qux"),
array("lemon lime", "banana", "apple"));
}

@Test
void providesArgumentsWithStringDelimiter() {
var annotation = csvFileSource()//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ static MockCsvFileSourceBuilder csvFileSource() {

// -------------------------------------------------------------------------

private char quoteCharacter = '\0';
protected char delimiter = '\0';
protected String delimiterString = "";
protected String emptyValue = "";
Expand All @@ -46,6 +47,11 @@ private MockCsvAnnotationBuilder() {

protected abstract B getSelf();

B quoteCharacter(char quoteCharacter) {
this.quoteCharacter = quoteCharacter;
return getSelf();
}

B delimiter(char delimiter) {
this.delimiter = delimiter;
return getSelf();
Expand Down Expand Up @@ -84,7 +90,10 @@ static class MockCsvSourceBuilder extends MockCsvAnnotationBuilder<CsvSource, Mo

private String[] lines = new String[0];
private String textBlock = "";
private char quoteCharacter = '\'';

private MockCsvSourceBuilder() {
super.quoteCharacter = '\'';
}

@Override
protected MockCsvSourceBuilder getSelf() {
Expand All @@ -101,16 +110,12 @@ MockCsvSourceBuilder textBlock(String textBlock) {
return this;
}

MockCsvSourceBuilder quoteCharacter(char quoteCharacter) {
this.quoteCharacter = quoteCharacter;
return this;
}

@Override
CsvSource build() {
var annotation = mock(CsvSource.class);

// Common
when(annotation.quoteCharacter()).thenReturn(super.quoteCharacter);
when(annotation.delimiter()).thenReturn(super.delimiter);
when(annotation.delimiterString()).thenReturn(super.delimiterString);
when(annotation.emptyValue()).thenReturn(super.emptyValue);
Expand All @@ -121,7 +126,6 @@ CsvSource build() {
// @CsvSource
when(annotation.value()).thenReturn(this.lines);
when(annotation.textBlock()).thenReturn(this.textBlock);
when(annotation.quoteCharacter()).thenReturn(this.quoteCharacter);

return annotation;
}
Expand All @@ -136,6 +140,10 @@ static class MockCsvFileSourceBuilder extends MockCsvAnnotationBuilder<CsvFileSo
private String lineSeparator = "\n";
private int numLinesToSkip = 0;

private MockCsvFileSourceBuilder() {
super.quoteCharacter = '"';
}

@Override
protected MockCsvFileSourceBuilder getSelf() {
return this;
Expand Down Expand Up @@ -171,6 +179,7 @@ CsvFileSource build() {
var annotation = mock(CsvFileSource.class);

// Common
when(annotation.quoteCharacter()).thenReturn(super.quoteCharacter);
when(annotation.delimiter()).thenReturn(super.delimiter);
when(annotation.delimiterString()).thenReturn(super.delimiterString);
when(annotation.emptyValue()).thenReturn(super.emptyValue);
Expand Down

0 comments on commit 8c74569

Please sign in to comment.