Class ConnectorOutputTest
- Direct Known Subclasses:
SimpleSourceConnectorOutputTest
Overview
One of the common and easiest ways to test a connector is to simply run it and compare the changes events output by the connector with a sequence of expected change events. Manually writing tests to compare the results is tedious and error prone, so an alternative is to run the test once and record the change events output by the connector so that these change events can be used as expected results in subsequent runs of the same test.This framework makes it possible to define tests that start a connector, record the change events output by the connector, and compare these change events to expected results. Connectors are defined via properties files, and the expected results are recorded to or read from files containing the sequence of change events serialized as an array of JSON documents. The test can also define an optional environment properties file in which test-specific parameters can be specified.
Variations in results
Some fields in the output will change every time the connector is run. For example, a change event may include the timestamp at which the connector processed/generated the event, or it might include a transaction identifier that is different with each run. The framework works around these issues by allowing the test to specify in the environment parameters the fields in the change events that should be ignored when comparing actual change events to expected change events.
Other fields may vary every time the database is started. For example, a MySQL server that is configured to use GTIDs will
generate a new server_uuid value when it is initialized, and this value is then used in all GTIDs generated by
that server instance. Since multiple builds will start up and initialize a new MySQL server (in a Docker container), the
GTID will vary with each build. The framework can handle this by allowing the expected results to contain variables in
the form of ${variableName1}, and the framework will use values supplied during test setup to replace these
variables prior to parsing the JSON document for each expected change event. The framework allows the test setup to
supply a function that provides the replacement variable values given the connector configuration. So the MySQL connector
tests, for example, can connect to the MySQL server and return all of the system variables as the variable values;
the framework does all the rest.
Stopping the connector
When the test framework has compared all of the generated change events to the expected change events and found no discrepancies, the framework knows that the connector should generate no additional change events. If the connector does generate another event, the test will fail. Otherwise, the framework will stop the connector since it is no longer needed.Connector restarts
Another requirement is to verify that a connector will resume where it left off when it is restarted following a failure or graceful shutdown. This is normally a bit harder to do, but this framework makes it very easy. Simply insert in the sequence of expected change events a restart command. The framework consumes each change event generated by the connector one at a time, and for each it compares it to the expected change event. As the framework comes across a restart command in the expected results, it knows to stop the connector (persisting the offsets in the last change event processed by the connector) and to then restart it given those persisted offsets. A properly-written connector will resume exactly where it left off, and the framework can once again begin comparing the resulting change events to the next events in the expected results.Example
A simple test runs a connector and records/compares the connector's output to expected results:
@Test
public void shouldCaptureChangesFromMySqlWithNoGtids() {
runConnector(usingSpec("test1", "src/test/resources/expected/test1"));
}
The framework uses a test specification that encapsulates the connector configuration, the environment properties, how
to obtain the variable values, and where to find the results. This example creates a test specification with the name
test1 and the connector properties file, the environment properties file, and the expected results file all defined
in a directory named src/test/resources/expected/test1. Once again, the test will write out the expected results
file if none exists; otherwise, it will read in the expected results and compare them to the events output by the connector.
The framework supports other variations, and the specWith method is a simple convenience method that replaces
explicitly building up the test specification by using lots of defaults. Consequently, the framework offers a great deal
of flexibility.
To see more detail about the actual and expected records, turn on Debezium's test debug mode by using this line
before the runConnector call:
Testing.Debug.enable();
- Author:
- Randall Hauch
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate static classprivate static enumprotected static interfaceprotected static interfaceprivate static classstatic interfacestatic classprotected static interface -
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final PathBefore each test this class will delete the "target/connector-output" directory under the project in which the test is run.static final Stringstatic final Stringstatic final Stringstatic final Stringstatic final Stringstatic final Stringstatic final Stringstatic final Stringstatic final Stringprivate static final org.slf4j.Loggerprotected static final Path -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprotected voidaddValueComparatorsByFieldPath(BiConsumer<String, VerifyRecord.RecordValueComparator> comparatorsByPath) protected voidaddValueComparatorsBySchemaName(BiConsumer<String, VerifyRecord.RecordValueComparator> comparatorsByPath) voidprivate voidapplyCommand(Document record, ConnectorOutputTest.ConsumerCompletion result) private voidassertSourceRecordMatch(org.apache.kafka.connect.source.SourceRecord actual, org.apache.kafka.connect.source.SourceRecord expected, Predicate<String> ignoreFields, Map<String, VerifyRecord.RecordValueComparator> comparatorsByName, Map<String, VerifyRecord.RecordValueComparator> comparatorsBySchemaName) voidprivate <T> Queue<T>fixedSizeQueue(int size) private StringgetCommand(Document record) protected String[]Return the names of the fields that should always be ignored for all tests.private booleanprivate booleanisEndCommand(Document record) private org.apache.kafka.connect.source.SourceRecordrehydrateSourceRecord(Document record, ConnectorOutputTest.SchemaAndValueConverter keyConverter, ConnectorOutputTest.SchemaAndValueConverter valueConverter) protected static FilereplaceVariables(InputStream stream, AvailableVariables variables) Read the contents of the suppliedInputStream, replace all variables found in the content, and write the result to a temporary file.protected voidRun the connector described by the supplied test specification.protected voidrunConnector(ConnectorOutputTest.TestSpecification spec, io.debezium.engine.DebeziumEngine.CompletionCallback callback) Run the connector described by the supplied test specification.protected voidrunConnector(String testName, String directory) Run the connector that uses the files in the given directory for the connector configuration, environment configuration, and expected results.protected voidrunConnector(String testName, String configFile, String expectedRecordsFile, String envFile) Run the connector described by the supplied test specification.protected voidrunConnector(String testName, Path directory) Run the connector that uses the files in the given directory for the connector configuration, environment configuration, and expected results.protected voidrunConnector(String testName, Path configFile, Path expectedRecordsFile, Path envFile) Run the connector described by the supplied test specification.private DocumentserializeSourceRecord(org.apache.kafka.connect.source.SourceRecord record, ConnectorOutputTest.SchemaAndValueConverter keyConverter, ConnectorOutputTest.SchemaAndValueConverter valueConverter) Serialize the source record to document form.protected ConnectorOutputTest.TestSpecificationCreate a new test specification with the given name.protected ConnectorOutputTest.TestSpecificationCreate a new test specification that uses the files in the given directory for the connector configuration, environment configuration, and expected results.protected ConnectorOutputTest.TestSpecificationCreate a new test specification that uses the given files for the configuration, environment, and expected results.protected ConnectorOutputTest.TestSpecificationCreate a new test specification that uses the files in the given directory for the connector configuration, environment configuration, and expected results.protected ConnectorOutputTest.TestSpecificationCreate a new test specification that uses the given files for the configuration, environment, and expected results.
-
Field Details
-
LOGGER
private static final org.slf4j.Logger LOGGER -
DEFAULT_CONNECTOR_PROPERTIES_FILENAME
- See Also:
-
DEFAULT_ENV_PROPERTIES_FILENAME
- See Also:
-
DEFAULT_EXPECTED_RECORDS_FILENAME
- See Also:
-
ENV_CONNECTOR_TIMEOUT_IN_SECONDS
- See Also:
-
ENV_IGNORE_FIELDS
- See Also:
-
CONNECTOR_OUTPUT_PATH
Before each test this class will delete the "target/connector-output" directory under the project in which the test is run. -
OFFSET_STORE_PATH
-
CONTROL_KEY
- See Also:
-
CONTROL_RESTART
- See Also:
-
CONTROL_STOP
- See Also:
-
CONTROL_END
- See Also:
-
-
Constructor Details
-
ConnectorOutputTest
public ConnectorOutputTest()
-
-
Method Details
-
addValueComparatorsByFieldPath
protected void addValueComparatorsByFieldPath(BiConsumer<String, VerifyRecord.RecordValueComparator> comparatorsByPath) -
addValueComparatorsBySchemaName
protected void addValueComparatorsBySchemaName(BiConsumer<String, VerifyRecord.RecordValueComparator> comparatorsByPath) -
usingSpec
Create a new test specification with the given name.- Parameters:
name- the name of the test- Returns:
- the test specification; never null
-
usingSpec
Create a new test specification that uses the files in the given directory for the connector configuration, environment configuration, and expected results. These names of these files are as follows:- "connector.properties" for the connector configuration file and is required;
- "connector.properties" for the environment configuration file and is optional; and
- "expected-records.json" for the expected results file and will be generated if missing.
- Parameters:
name- the name of the testdirectory- the path to the directory that contains the test files; may not be null- Returns:
- the test specification; never null
-
usingSpec
Create a new test specification that uses the files in the given directory for the connector configuration, environment configuration, and expected results. These names of these files are as follows:- "connector.properties" for the connector configuration file and is required;
- "connector.properties" for the environment configuration file and is optional; and
- "expected-records.json" for the expected results file and will be generated if missing.
- Parameters:
name- the name of the testdirectory- the path to the directory that contains the test files; may not be null- Returns:
- the test specification; never null
-
usingSpec
protected ConnectorOutputTest.TestSpecification usingSpec(String name, String configFile, String expectedRecordsFile, String envFile) Create a new test specification that uses the given files for the configuration, environment, and expected results.- Parameters:
name- the name of the test or test run; may not be nullconfigFile- the path to the configuration file; may not be nullexpectedRecordsFile- the path to the file where the expected records can be read or where they are to be written; may not be nullenvFile- the path to the file containing the environment properties; may be null if there is no such file- Returns:
- the test specification; never null
-
usingSpec
protected ConnectorOutputTest.TestSpecification usingSpec(String name, Path configFile, Path expectedRecordsFile, Path envFile) Create a new test specification that uses the given files for the configuration, environment, and expected results.- Parameters:
name- the name of the test or test run; may not be nullconfigFile- the path to the configuration file; may not be nullexpectedRecordsFile- the path to the file where the expected records can be read or where they are to be written; may not be nullenvFile- the path to the file containing the environment properties; may be null if there is no such file- Returns:
- the test specification; never null
-
cleanOffsetStorage
public void cleanOffsetStorage() -
afterEachTestMethod
public void afterEachTestMethod() -
globallyIgnorableFieldNames
Return the names of the fields that should always be ignored for all tests. By default this method returnsnull.- Returns:
- the array of field names that should always be ignored, or empty or null if there are no such fields
-
runConnector
Run the connector that uses the files in the given directory for the connector configuration, environment configuration, and expected results. These names of these files are as follows:- "connector.properties" for the connector configuration file and is required;
- "connector.properties" for the environment configuration file and is optional; and
- "expected-records.json" for the expected results file and will be generated if missing.
- Parameters:
testName- the name of the test or test run; may not be nulldirectory- the path to the directory that contains the test files; may not be null
-
runConnector
Run the connector that uses the files in the given directory for the connector configuration, environment configuration, and expected results. These names of these files are as follows:- "connector.properties" for the connector configuration file and is required;
- "connector.properties" for the environment configuration file and is optional; and
- "expected-records.json" for the expected results file and will be generated if missing.
- Parameters:
testName- the name of the test or test run; may not be nulldirectory- the path to the directory that contains the test files; may not be null
-
runConnector
protected void runConnector(String testName, String configFile, String expectedRecordsFile, String envFile) Run the connector described by the supplied test specification.- Parameters:
testName- the name of the test or test run; may not be nullconfigFile- the path to the configuration file; may not be nullexpectedRecordsFile- the path to the file where the expected records can be read or where they are to be written; may not be nullenvFile- the path to the file containing the environment properties; may be null if there is no such file
-
runConnector
protected void runConnector(String testName, Path configFile, Path expectedRecordsFile, Path envFile) Run the connector described by the supplied test specification.- Parameters:
testName- the name of the test or test run; may not be nullconfigFile- the path to the configuration file; may not be nullexpectedRecordsFile- the path to the file where the expected records can be read or where they are to be written; may not be nullenvFile- the path to the file containing the environment properties; may be null if there is no such file
-
runConnector
Run the connector described by the supplied test specification.- Parameters:
spec- the test specification
-
runConnector
protected void runConnector(ConnectorOutputTest.TestSpecification spec, io.debezium.engine.DebeziumEngine.CompletionCallback callback) Run the connector described by the supplied test specification.- Parameters:
spec- the test specificationcallback- the function that should be called when the connector is stopped
-
applyCommand
-
isCommand
-
isEndCommand
-
getCommand
-
rehydrateSourceRecord
private org.apache.kafka.connect.source.SourceRecord rehydrateSourceRecord(Document record, ConnectorOutputTest.SchemaAndValueConverter keyConverter, ConnectorOutputTest.SchemaAndValueConverter valueConverter) throws IOException - Throws:
IOException
-
serializeSourceRecord
private Document serializeSourceRecord(org.apache.kafka.connect.source.SourceRecord record, ConnectorOutputTest.SchemaAndValueConverter keyConverter, ConnectorOutputTest.SchemaAndValueConverter valueConverter) throws IOException Serialize the source record to document form.- Parameters:
record- the record; may not be nullkeyConverter- the converter for the record key's schema and payloadvalueConverter- the converter for the record value's schema and payload- Returns:
- the document form of the source record; never null
- Throws:
IOException- if there is an error converting the key or value
-
assertSourceRecordMatch
private void assertSourceRecordMatch(org.apache.kafka.connect.source.SourceRecord actual, org.apache.kafka.connect.source.SourceRecord expected, Predicate<String> ignoreFields, Map<String, VerifyRecord.RecordValueComparator> comparatorsByName, Map<String, VerifyRecord.RecordValueComparator> comparatorsBySchemaName) -
toMap
-
fixedSizeQueue
-
replaceVariables
protected static File replaceVariables(InputStream stream, AvailableVariables variables) throws IOException Read the contents of the suppliedInputStream, replace all variables found in the content, and write the result to a temporary file.- Parameters:
stream- the input stream containing zero or morevariable expressionsvariables- the variables- Returns:
- the temporary file that exists in the data directory
- Throws:
IOException- if there is a problem reading the input stream or writing to the temporary file
-