Class 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_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
    • Constructor Detail

      • ConnectorOutputTest

        public ConnectorOutputTest()
    • Method Detail

      • 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:
        Parameters:
        name - the name of the test
        directory - 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:
        Parameters:
        name - the name of the test
        directory - 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 null
        configFile - the path to the configuration file; may not be null
        expectedRecordsFile - the path to the file where the expected records can be read or where they are to be written; may not be null
        envFile - 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 null
        configFile - the path to the configuration file; may not be null
        expectedRecordsFile - the path to the file where the expected records can be read or where they are to be written; may not be null
        envFile - 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 returns null.
        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:
        Parameters:
        testName - the name of the test or test run; may not be null
        directory - 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:
        Parameters:
        testName - the name of the test or test run; may not be null
        directory - 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 null
        configFile - the path to the configuration file; may not be null
        expectedRecordsFile - the path to the file where the expected records can be read or where they are to be written; may not be null
        envFile - 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 null
        configFile - the path to the configuration file; may not be null
        expectedRecordsFile - the path to the file where the expected records can be read or where they are to be written; may not be null
        envFile - 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 specification
        callback - the function that should be called when the connector is stopped
      • isCommand

        private boolean isCommand​(Document record)
      • isEndCommand

        private boolean isEndCommand​(Document record)
      • 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 null
        keyConverter - the converter for the record key's schema and payload
        valueConverter - 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
      • fixedSizeQueue

        private <T> Queue<T> fixedSizeQueue​(int size)
      • replaceVariables

        protected static File replaceVariables​(InputStream stream,
                                               AvailableVariables variables)
                                        throws IOException
        Read the contents of the supplied InputStream, replace all variables found in the content, and write the result to a temporary file.
        Parameters:
        stream - the input stream containing zero or more variable expressions
        variables - 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