Skip to content

Commit

Permalink
#3: added test and documentation for resultSets matcher
Browse files Browse the repository at this point in the history
  • Loading branch information
AnastasiiaSergienko committed May 22, 2020
1 parent 33fe535 commit 54d2f8a
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 69 deletions.
7 changes: 6 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ language: java
matrix:
include:
- jdk: openjdk11

addons:
sonarcloud:
organization: exasol

script:
- mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install -Dsonar.projectKey=com.exasol:hamcrest-resultset-matcher -Dsonar.login=${SONAR_TOKEN}
- mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install sonar:sonar
cache:
directories:
- "$HOME/.m2"
25 changes: 24 additions & 1 deletion doc/user_guide/user_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,30 @@ Depending on your personal preferences or the use case for the test, you can pic

## The `ResultSetMatcher`

TODO: add
This matcher allows you to compare two `ResultSet`'s. This can be helpful when you need, for example, to compare each value of two tables.

A minimal test would then look as in the example below.

```java
import java.sql.ResultSet;import static org.hamcrest.MatcherAssert.assertThat;

// ...

class CustomerTablePopulationTest {
@Test
void testTableContents() {
// ...
final ResulSet table1 = statement1.execute("SELECT * FROM CUSTOMERS");
final ResultSet table2 = statement2.execute("SELECT * FROM CUSTOMERS2");

assertThat(table1, matchesResultSet(table2));
}
}
```

Please keep in mind that you need to have two opened `ResultSet`s for this matcher.
Some JDBC drivers close the previously opened `ResultSet` as soon as you execute the next query on a `Statement`.
So you might be need to have two statements as shown in the example above.

## The `ResultSetStructureMatcher`

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.exasol</groupId>
<artifactId>hamcrest-resultset-matcher</artifactId>
<version>0.2.0</version>
<version>1.0.0</version>
<name>Matcher for SQL Result Sets</name>
<description>This project provides hamcrest matcher that compares java.sql.ResultSet objects.</description>
<url>https://github.com/exasol/hamcrest-resultset-matcher</url>
Expand Down
109 changes: 71 additions & 38 deletions src/main/java/com/exasol/matcher/ResultSetMatcher.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
package com.exasol.matcher;

import java.math.BigDecimal;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.sql.*;

import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
Expand All @@ -16,8 +12,10 @@
public final class ResultSetMatcher extends TypeSafeMatcher<ResultSet> {
private static final int EXASOL_INTERVAL_DAY_TO_SECONDS = -104;
private static final int EXASOL_INTERVAL_YEAR_TO_MONTHS = -103;
private final StringBuilder errorMessage = new StringBuilder();
private final ResultSet expectedResultSet;
private String expectedDescription = "";
private String actualDescription = "";
private int rowCounter = 0;

/**
* Creates a new instance of {@link ResultSetMatcher}.
Expand Down Expand Up @@ -51,45 +49,76 @@ protected boolean matchesSafely(final ResultSet actualResultSet) {

@Override
protected void describeMismatchSafely(final ResultSet item, final Description mismatchDescription) {
mismatchDescription.appendText(this.errorMessage.toString());
super.describeMismatchSafely(item, mismatchDescription);
mismatchDescription.appendText(this.actualDescription);
}

@Override
public void describeTo(final Description description) {
description.appendValue(this.expectedResultSet);
description.appendText(this.expectedDescription);
}

private boolean assertEqualResultSets(final ResultSet actualResultSet) throws SQLException {
final int expectedColumnCount = this.expectedResultSet.getMetaData().getColumnCount();
final int actualColumnCount = actualResultSet.getMetaData().getColumnCount();
if (expectedColumnCount != actualColumnCount) {
this.errorMessage.append("Column count doesn't match. Expected column count: ").append(expectedColumnCount);
this.errorMessage.append(", actual column count: ").append(actualColumnCount).append("\n");
if (!columnCounterMatches(expectedColumnCount, actualColumnCount)) {
return false;
}
boolean expectedNext;
int rowCounter = 0;
boolean actualNext;
do {
expectedNext = this.expectedResultSet.next();
rowCounter++;
if ((expectedNext != actualResultSet.next())
|| (this.expectedResultSet.isLast() != actualResultSet.isLast())) {
this.errorMessage.append("Expected and actual result sets have different number of rows.\n");
actualNext = actualResultSet.next();
this.rowCounter++;
if (!bothRowsExist(actualResultSet, expectedNext, actualNext))
return false;
}
if (expectedNext && !doesRowMatch(actualResultSet, expectedColumnCount)) {
this.errorMessage.append(", row ").append(rowCounter).append(")\n");
return false;
}
} while (expectedNext);
} while (actualNext);
return true;
}

private boolean columnCounterMatches(final int expectedColumnCount, final int actualColumnCount) {
if (expectedColumnCount != actualColumnCount) {
this.expectedDescription = "ResultSet with <" + expectedColumnCount + "> column(s)";
this.actualDescription = "ResultSet with <" + actualColumnCount + "> column(s)";
return false;
} else {
return true;
}
}

private boolean bothRowsExist(final ResultSet actualResultSet, final boolean expectedNext, final boolean actualNext)
throws SQLException {
if (expectedNext != actualNext) {
final int expectedRowCounter;
final int actualRowCounter;
if (expectedNext) {
expectedRowCounter = getRowCounter(rowCounter, this.expectedResultSet);
actualRowCounter = rowCounter - 1;
} else {
expectedRowCounter = rowCounter - 1;
actualRowCounter = getRowCounter(rowCounter, actualResultSet);
}
this.expectedDescription = "ResultSet with <" + expectedRowCounter + "> row(s)";
this.actualDescription = "ResultSet with <" + actualRowCounter + "> row(s)";
return false;
} else {
return true;
}
}

private int getRowCounter(final int rowCounter, final ResultSet resultSet) throws SQLException {
int counter = rowCounter;
while (resultSet.next()) {
counter++;
}
return counter;
}

private boolean doesRowMatch(final ResultSet actualResultSet, final int expectedColumnCount) throws SQLException {
for (int column = 1; column <= expectedColumnCount; ++column) {
if (!doesFieldMatch(actualResultSet, column)) {
this.errorMessage.append(" (column ").append(column);
return false;
}
}
Expand All @@ -102,8 +131,8 @@ private boolean doesFieldMatch(final ResultSet actualRow, final int column) thro
if (resultSetTypeExpected == resultSetTypeActual) {
return doesValueMatch(actualRow, column, resultSetTypeExpected);
} else {
this.errorMessage.append("Data type does not match. Expected: ").append(resultSetTypeExpected);
this.errorMessage.append(", actual: ").append(resultSetTypeActual);
this.expectedDescription = "Column <" + column + "> with JDBC Data Type " + resultSetTypeExpected;
this.actualDescription = "Column <" + column + "> with JDBC Data Type " + resultSetTypeActual;
return false;
}
}
Expand Down Expand Up @@ -139,70 +168,74 @@ private boolean doesValueMatch(final ResultSet actualRow, final int column, fina
private boolean doesTimestampMatch(final ResultSet actualRow, final int column) throws SQLException {
final Timestamp expected = this.expectedResultSet.getTimestamp(column);
final Timestamp actual = actualRow.getTimestamp(column);
return doesObjectMatch("Timestamp", expected, actual);
return doesObjectMatch("Timestamp", expected, actual, column);
}

private boolean doesDateMatch(final ResultSet actualRow, final int column) throws SQLException {
final Date expected = this.expectedResultSet.getDate(column);
final Date actual = actualRow.getDate(column);
return doesObjectMatch("Date", expected, actual);
return doesObjectMatch("Date", expected, actual, column);
}

private boolean doesDecimalMatch(final ResultSet actualRow, final int column) throws SQLException {
final BigDecimal expected = this.expectedResultSet.getBigDecimal(column);
final BigDecimal actual = actualRow.getBigDecimal(column);
return doesObjectMatch("BigDecimal", expected, actual);
return doesObjectMatch("BigDecimal", expected, actual, column);
}

private boolean doesBooleanMatch(final ResultSet actualRow, final int column) throws SQLException {
final boolean expected = this.expectedResultSet.getBoolean(column);
final boolean actual = actualRow.getBoolean(column);
return doesPrimitiveTypeMatch("Boolean", expected, actual);
return doesPrimitiveTypeMatch("Boolean", expected, actual, column);
}

private <T> boolean doesPrimitiveTypeMatch(final String dataTypeName, final T expectedValue, final T actualValue) {
private <T> boolean doesPrimitiveTypeMatch(final String dataTypeName, final T expectedValue, final T actualValue,
final int column) {
if (expectedValue == actualValue) {
return true;
} else {
writeFieldValueMismatchErrorMessage(dataTypeName, String.valueOf(expectedValue),
String.valueOf(actualValue));
String.valueOf(actualValue), column);
return false;
}
}

private void writeFieldValueMismatchErrorMessage(final String valueType, final String expectedValue,
final String actualValue) {
this.errorMessage.append(valueType).append(" field value does not match. Expected: ").append(expectedValue);
this.errorMessage.append(", actual: ").append(actualValue);
final String actualValue, final int column) {
this.expectedDescription = valueType + " field value <" + expectedValue + ">" + " (column " + column + ", row "
+ this.rowCounter + ")";
this.actualDescription = valueType + " field value <" + actualValue + ">" + " (column " + column + ", row "
+ this.rowCounter + ")";
}

private boolean doesDoubleMatch(final ResultSet actualRow, final int column) throws SQLException {
final Double expected = this.expectedResultSet.getDouble(column);
final Double actual = actualRow.getDouble(column);
return doesObjectMatch("Double", expected, actual);
return doesObjectMatch("Double", expected, actual, column);
}

private boolean doesIntegerMatch(final ResultSet actualRow, final int column) throws SQLException {
final Integer expected = this.expectedResultSet.getInt(column);
final Integer actual = actualRow.getInt(column);
return doesObjectMatch("Integer", expected, actual);
return doesObjectMatch("Integer", expected, actual, column);
}

private boolean doesStringMatch(final ResultSet actualRow, final int column) throws SQLException {
final String expected = this.expectedResultSet.getString(column);
final String actual = actualRow.getString(column);
return doesObjectMatch("String", expected, actual);
return doesObjectMatch("String", expected, actual, column);
}

private <T> boolean doesObjectMatch(final String dataTypeName, final T expectedValue, final T actualValue) {
private <T> boolean doesObjectMatch(final String dataTypeName, final T expectedValue, final T actualValue,
final int column) {
if ((expectedValue == null) || (actualValue == null)) {
return doesPrimitiveTypeMatch(dataTypeName, expectedValue, actualValue);
return doesPrimitiveTypeMatch(dataTypeName, expectedValue, actualValue, column);
}
if (expectedValue.equals(actualValue)) {
return true;
} else {
writeFieldValueMismatchErrorMessage(dataTypeName, String.valueOf(expectedValue),
String.valueOf(actualValue));
String.valueOf(actualValue), column);
return false;
}
}
Expand Down
23 changes: 23 additions & 0 deletions src/test/java/com/exasol/matcher/AbstractResultSetMatcherTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.exasol.matcher;

import java.sql.*;

public class AbstractResultSetMatcherTest {
protected Statement statement;

protected void execute(final String sql) {
try {
this.statement.execute(sql);
} catch (final SQLException execption) {
throw new AssertionError("Unable to execute SQL statement: " + sql, execption);
}
}

protected ResultSet query(final String sql) {
try {
return this.statement.executeQuery(sql);
} catch (final SQLException exeption) {
throw new AssertionError("Unable to run query: " + sql, exeption);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,17 @@
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.*;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.StringDescription;
import org.hamcrest.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class ResultSetAgainstObjectMatcherTest {
private Statement statement;

class ResultSetAgainstObjectMatcherTest extends AbstractResultSetMatcherTest {
@BeforeEach
void beforeEach() throws SQLException {
final Connection connection = DriverManager.getConnection("jdbc:derby:memory:test;create=true");
this.statement = connection.createStatement();
statement = connection.createStatement();
}

@Test
Expand All @@ -34,22 +26,6 @@ void testMatchSimpleResultset() {
assertThat(query("SELECT * FROM SIMPLE"), table().row("foo", 1).row("bar", 2).matches());
}

private void execute(final String sql) {
try {
this.statement.execute(sql);
} catch (final SQLException execption) {
throw new AssertionError("Unable to execute SQL statement: " + sql, execption);
}
}

private ResultSet query(final String sql) {
try {
return this.statement.executeQuery(sql);
} catch (final SQLException execption) {
throw new AssertionError("Unable to run query: " + sql, execption);
}
}

@Test
void testDetectTooManyRows() {
execute("CREATE TABLE TO_MANY_ROWS(COL1 VARCHAR(20), COL2 INTEGER)");
Expand Down
Loading

0 comments on commit 54d2f8a

Please sign in to comment.