Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/changes/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
<action dev="jeffjensen" type="add" issue="938" system="github" due-to="jeffjensen">
Add org.dbunit.PrepAndExpectedTestData, an immutable value bundling the VerifyTableDefinition[], prep String[], and expected String[] that PrepAndExpectedTestCase.configureTest(), preTest(), and runTest() otherwise take as three separate arguments; it copies each array in and out, normalizes a null array to empty, and provides a NONE constant and a prepOnly(String...) factory. Add default configureTest(PrepAndExpectedTestData), preTest(PrepAndExpectedTestData), and runTest(PrepAndExpectedTestData, PrepAndExpectedTestCaseSteps) overloads that unpack the bundle and delegate to the existing array methods - purely additive, no existing signature changes. Aimed at data-driven tests, where the triple otherwise threads through every @ParameterizedTest signature and @MethodSource row.
</action>
<action dev="jeffjensen" type="add" issue="973" system="github" due-to="jeffjensen">
Add RegularExpressionValueComparer to org.dbunit.assertion.comparer.value: a ValueComparer that reads the expected dataset value as a regular expression and passes when it matches the actual value in its entirety (Matcher.matches(), the same whole-value rule as String.matches()), for verifying columns whose format a test controls but whose exact content it does not, such as database-generated ids, UUID columns, or timestamps rendered into a text column. A partial match is opt-in by making the pattern permissive at both ends, and an invalid pattern raises DatabaseUnitException naming the row and column. Also exposed as the ValueComparers.regularExpressionValueComparer constant, since it adds no dependency beyond java.util.regex.
</action>
</release>
<release version="3.5.2" date="Sep 7, 2026" description="DefaultPrepAndExpectedTestCase connection hygiene: replace a connection the pool or database dropped between tests, and warn when handed a non-autocommit connection">
<action dev="jeffjensen" type="fix" issue="962" system="github" due-to="jeffjensen">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package org.dbunit.assertion.comparer.value;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.dbunit.DatabaseUnitException;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.datatype.DataType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* {@link ValueComparer} implementation that verifies the actual value matches
* the regular expression supplied as the expected value.
*
* <p>
* The expected value, converted to a {@link String}, is the
* {@link java.util.regex.Pattern regular expression}; the actual value, also
* converted to a {@link String}, is the input tested against it. This mirrors
* how {@link IsActualContainingExpectedStringValueComparer} treats the expected
* value as the substring to look for.
*
* <p>
* The comparison succeeds only when the pattern matches the <em>entire</em>
* actual value, using {@link Matcher#matches()}, consistent with
* {@link String#matches(String)}. To match only part of the actual value, make
* the pattern permissive at both ends, for example <code>.*[0-9]{4}.*</code>.
* A <code>.</code> does not match a line terminator unless the
* {@link Pattern#DOTALL DOTALL} flag is set, so prefix such a pattern with
* <code>(?s)</code> when the actual value may contain a newline, for example
* <code>(?s).*[0-9]{4}.*</code>.
*
* <p>
* Useful for columns whose exact content a test does not control but whose
* format it does, such as database-generated identifiers, UUID columns, or
* timestamps rendered into a text column.
*
* <p>
* Special case: if both values are null, they match; if exactly one is null,
* they do not.
*
* <p>
* This comparer adds no dependency beyond {@code java.util.regex}, so it is also
* available as {@link ValueComparers#regularExpressionValueComparer}.
*
* @author Jeff Jensen
* @since 3.6.0
*/
public class RegularExpressionValueComparer extends ValueComparerTemplateBase
{
private final Logger log = LoggerFactory.getLogger(getClass());

@Override
protected boolean isExpected(final ITable expectedTable,
final ITable actualTable, final int rowNum, final String columnName,
final DataType dataType, final Object expectedValue,
final Object actualValue) throws DatabaseUnitException
{
final boolean isExpected;

// handle nulls: prevent NPE and isExpected=true when both null
if (expectedValue == null && actualValue == null)
{
// both are null, so match
isExpected = true;
} else if (expectedValue == null || actualValue == null)
{
// both aren't null, one is null, so no match
isExpected = false;
} else
{
// neither are null, so compare
isExpected = isMatching(rowNum, columnName, expectedValue,
actualValue);
}

return isExpected;
}

/**
* Returns whether the regular expression held in the expected value matches
* the whole actual value, both converted to strings.
*
* @param rowNum
* The current row number comparing, used only to identify an
* invalid pattern.
* @param columnName
* The name of the current column comparing, used only to identify
* an invalid pattern.
* @param expectedValue
* The expected value, holding the regular expression.
* @param actualValue
* The actual value tested against the regular expression.
* @return <code>true</code> if the regular expression matches the entire
* actual value string.
* @throws DatabaseUnitException
* If either value cannot be converted to a string, or the
* expected value is not a valid regular expression.
*/
protected boolean isMatching(final int rowNum, final String columnName,
final Object expectedValue, final Object actualValue)
throws DatabaseUnitException
{
final String regex = DataType.asString(expectedValue);
final String actualValueString = DataType.asString(actualValue);
final Pattern pattern = compilePattern(rowNum, columnName, regex);
final Matcher matcher = pattern.matcher(actualValueString);
final boolean isMatching = matcher.matches();
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
log.debug("isMatching: regex={}, actualValueString={}, isMatching={}",
regex, actualValueString, isMatching);

return isMatching;
}

/**
* Compiles the expected value into a {@link Pattern}, turning an invalid
* expression into a {@link DatabaseUnitException} identifying the row and
* column, consistent with how
* {@link IsActualEqualToExpectedJsonValueComparer} reports an unparseable
* expected value.
*/
private Pattern compilePattern(final int rowNum, final String columnName,
final String regex) throws DatabaseUnitException
{
try
{
return Pattern.compile(regex);
} catch (final PatternSyntaxException e)
{
final String message = String.format(
"Unable to compile expected value as a regular expression"
+ " for column '%s', row %d: %s",
columnName, rowNum, regex);
throw new DatabaseUnitException(message, e);
}
}

@Override
protected String getFailPhrase()
{
return "not matching the regular expression";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,16 @@ protected ValueComparers()
public static final ValueComparer isActualContainingExpectedStringValueComparer =
new IsActualContainingExpectedStringValueComparer();

/**
* Checks whether the actual value matches, in its entirety, the regular
* expression given as the expected value.
*
* @see RegularExpressionValueComparer
* @since 3.6.0
*/
public static final ValueComparer regularExpressionValueComparer =
new RegularExpressionValueComparer();

/**
* Verifies nothing and never fails.
*
Expand Down
13 changes: 13 additions & 0 deletions src/site/asciidoc/datacomparisons/valuecomparer.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ It enables comparisons such as:
* greater-than
* less-than
* contains
* regular expression match
* complex multi-column-based
* dynamically choosing the ValueComparer based on criteria

Expand Down Expand Up @@ -46,6 +47,18 @@ plus pre-configured variances (e.g.
link:/dbunit/apidocs/org/dbunit/assertion/comparer/value/ValueComparers.html#isActualWithinOneMinuteNewerOfExpectedTimestamp[isActualWithinOneMinuteNewerOfExpectedTimestamp]).
Start with these as they provide for most comparison needs.

For a column whose exact content a test does not control but whose format it
does - a database-generated id, a UUID, a formatted identifier, a timestamp
rendered into a text column -
link:/dbunit/apidocs/org/dbunit/assertion/comparer/value/RegularExpressionValueComparer.html[RegularExpressionValueComparer]
(also
link:/dbunit/apidocs/org/dbunit/assertion/comparer/value/ValueComparers.html#regularExpressionValueComparer[ValueComparers.regularExpressionValueComparer])
takes the expected dataset value as a regular expression and passes when it
matches the actual value in its entirety, the same rule as
`java.lang.String.matches`. To match only part of the value, wrap the pattern in
`.*` on both ends, e.g. `.*[0-9]{4}.*`; a `.` does not cross a line terminator,
so prefix the pattern with `(?s)` when the actual value may contain a newline.

Some implementations are deliberately left off
link:/dbunit/apidocs/org/dbunit/assertion/comparer/value/ValueComparers.html[ValueComparers]
because it eagerly instantiates every instance it declares, and doing so there would force
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,42 @@ void testAssertWithValueComparer_isActualGreaterThan_failsWhenActualIsSmaller()
.isInstanceOf(DbComparisonFailure.class);
}

@Test
void testAssertWithValueComparer_regularExpression_passesWhenActualMatchesPattern()
throws Exception
{
final Column[] columns =
new Column[]{new Column("ID", DataType.VARCHAR)};

final DefaultTable expected = new DefaultTable("T", columns);
expected.addRow(new Object[]{"[0-9]+"});

final DefaultTable actual = new DefaultTable("T", columns);
actual.addRow(new Object[]{"4071"});

assertDoesNotThrow(() -> sut.assertWithValueComparer(expected, actual,
ValueComparers.regularExpressionValueComparer));
}

@Test
void testAssertWithValueComparer_regularExpression_failsWhenActualDoesNotMatchPattern()
throws Exception
{
final Column[] columns =
new Column[]{new Column("ID", DataType.VARCHAR)};

final DefaultTable expected = new DefaultTable("T", columns);
expected.addRow(new Object[]{"[0-9]+"});

final DefaultTable actual = new DefaultTable("T", columns);
actual.addRow(new Object[]{"4071-A"});

assertThatThrownBy(() -> sut.assertWithValueComparer(expected, actual,
ValueComparers.regularExpressionValueComparer))
.as("A value not matching the pattern should fail the comparison.")
.isInstanceOf(DbComparisonFailure.class);
}

@Test
void testAssertWithValueComparer_isActualNull_passesWhenActualIsNull()
throws Exception
Expand Down
Loading
Loading