Class ConnectorOutputTest
- java.lang.Object
-
- io.debezium.embedded.ConnectorOutputTest
-
- Direct Known Subclasses:
SimpleSourceConnectorOutputTest
public abstract class ConnectorOutputTest extends Object
A base class for classes the define integration tests for connectors that verify the output of a connector matches an expected sequence of change event records. This base class provides a simple framework for running a connector given a configuration and either recording or comparing the connector's output to expected results.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_uuidvalue 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 nametest1and the connector properties file, the environment properties file, and the expected results file all defined in a directory namedsrc/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
specWithmethod 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
runConnectorcall:Testing.Debug.enable();
- Author:
- Randall Hauch
-
-
Nested Class Summary
Nested Classes Modifier and Type Class Description private static classConnectorOutputTest.ConsumerCompletionprivate static classConnectorOutputTest.ExecutionResultprotected static interfaceConnectorOutputTest.InputStreamSupplierprotected static interfaceConnectorOutputTest.OutputStreamSupplierprivate static classConnectorOutputTest.SchemaAndValueConverterstatic interfaceConnectorOutputTest.TestDatastatic classConnectorOutputTest.TestSpecificationprotected static interfaceConnectorOutputTest.VariableSupplier
-
Field Summary
Fields Modifier and Type Field Description static PathCONNECTOR_OUTPUT_PATHBefore each test this class will delete the "target/connector-output" directory under the project in which the test is run.static StringCONTROL_ENDstatic StringCONTROL_KEYstatic StringCONTROL_RESTARTstatic StringCONTROL_STOPstatic StringDEFAULT_CONNECTOR_PROPERTIES_FILENAMEstatic StringDEFAULT_ENV_PROPERTIES_FILENAMEstatic StringDEFAULT_EXPECTED_RECORDS_FILENAMEstatic StringENV_CONNECTOR_TIMEOUT_IN_SECONDSstatic StringENV_IGNORE_FIELDSprotected static PathOFFSET_STORE_PATH
-
Constructor Summary
Constructors Constructor Description ConnectorOutputTest()
-
Method Summary
All Methods Static Methods Instance Methods Concrete Methods Modifier and Type Method Description protected voidaddValueComparatorsByFieldPath(BiConsumer<String,VerifyRecord.RecordValueComparator> comparatorsByPath)protected voidaddValueComparatorsBySchemaName(BiConsumer<String,VerifyRecord.RecordValueComparator> comparatorsByPath)voidafterEachTestMethod()private 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)voidcleanOffsetStorage()private <T> Queue<T>fixedSizeQueue(int size)private StringgetCommand(Document record)protected String[]globallyIgnorableFieldNames()Return the names of the fields that should always be ignored for all tests.private booleanisCommand(Document record)private 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 voidrunConnector(ConnectorOutputTest.TestSpecification spec)Run the connector described by the supplied test specification.protected voidrunConnector(ConnectorOutputTest.TestSpecification spec, io.debezium.embedded.EmbeddedEngine.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.private Map<String,?>toMap(Document doc)protected ConnectorOutputTest.TestSpecificationusingSpec(String name)Create a new test specification with the given name.protected ConnectorOutputTest.TestSpecificationusingSpec(String name, String directory)Create a new test specification that uses the files in the given directory for the connector configuration, environment configuration, and expected results.protected ConnectorOutputTest.TestSpecificationusingSpec(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.protected ConnectorOutputTest.TestSpecificationusingSpec(String name, Path directory)Create a new test specification that uses the files in the given directory for the connector configuration, environment configuration, and expected results.protected ConnectorOutputTest.TestSpecificationusingSpec(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.
-
-
-
Field Detail
-
DEFAULT_CONNECTOR_PROPERTIES_FILENAME
public static final String DEFAULT_CONNECTOR_PROPERTIES_FILENAME
- See Also:
- Constant Field Values
-
DEFAULT_ENV_PROPERTIES_FILENAME
public static final String DEFAULT_ENV_PROPERTIES_FILENAME
- See Also:
- Constant Field Values
-
DEFAULT_EXPECTED_RECORDS_FILENAME
public static final String DEFAULT_EXPECTED_RECORDS_FILENAME
- See Also:
- Constant Field Values
-
ENV_CONNECTOR_TIMEOUT_IN_SECONDS
public static final String ENV_CONNECTOR_TIMEOUT_IN_SECONDS
- See Also:
- Constant Field Values
-
ENV_IGNORE_FIELDS
public static final String ENV_IGNORE_FIELDS
- See Also:
- Constant Field Values
-
CONNECTOR_OUTPUT_PATH
public static final Path 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
protected static final Path OFFSET_STORE_PATH
-
CONTROL_KEY
public static final String CONTROL_KEY
- See Also:
- Constant Field Values
-
CONTROL_RESTART
public static final String CONTROL_RESTART
- See Also:
- Constant Field Values
-
CONTROL_STOP
public static final String CONTROL_STOP
- See Also:
- Constant Field Values
-
CONTROL_END
public static final String CONTROL_END
- See Also:
- Constant Field Values
-
-
Method Detail
-
addValueComparatorsByFieldPath
protected void addValueComparatorsByFieldPath(BiConsumer<String,VerifyRecord.RecordValueComparator> comparatorsByPath)
-
addValueComparatorsBySchemaName
protected void addValueComparatorsBySchemaName(BiConsumer<String,VerifyRecord.RecordValueComparator> comparatorsByPath)
-
usingSpec
protected ConnectorOutputTest.TestSpecification usingSpec(String name)
Create a new test specification with the given name.- Parameters:
name- the name of the test- Returns:
- the test specification; never null
-
usingSpec
protected ConnectorOutputTest.TestSpecification usingSpec(String name, String directory)
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, Path directory)
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
protected String[] 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
protected void runConnector(String testName, String directory)
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, Path directory)
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
protected void runConnector(ConnectorOutputTest.TestSpecification spec)
Run the connector described by the supplied test specification.- Parameters:
spec- the test specification
-
runConnector
protected void runConnector(ConnectorOutputTest.TestSpecification spec, io.debezium.embedded.EmbeddedEngine.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
private void applyCommand(Document record, ConnectorOutputTest.ConsumerCompletion result)
-
isCommand
private boolean isCommand(Document record)
-
isEndCommand
private boolean isEndCommand(Document record)
-
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)
-
fixedSizeQueue
private <T> Queue<T> fixedSizeQueue(int size)
-
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
-
-