Integration Testing

Integration tests are used to test more complex scenarios, examining the interaction between different components of the system. In our case, they are employed to test Provisioned IM integration entities defined in the repo directory. The core class of an integration test is IntegrationTestSpecification.

During the test startup, the IM context is created, and entities are loaded from the repo directory. You can define which entities are part of the test, including all entities or some subset. This is achieved by using the seedEntities method. If no entities are specified, all entities are loaded.


How to Use an Integration Test

Let's examine an example of an integration test. In this scenario, the test focuses on a route that reads products from a CSV file, utilizes a product mapper to map the CSV data to a list of products, and subsequently invokes a partition endpoint with the appropriate payload.

Groovy
class FileRouteGitTest extends IntegrationTestSpecification {

    def "should load data from csv to pricefx partition"() {

        given: 'mock pricefx endpoint and seed entities'
        mockPost('/pricefx/test-partition/loaddata/P')

        seedProperty("data.directory", temporaryFolder.toString())
        seedEntities([routePrn("load-products-route.xml"), mapperPrn("product-mapper.xml")])

        when: 'csv file is created in data directory'
        seedFile("data/products/products.csv")

        then: 'verify that route converts csv to json and sends it to pricefx'
        def conditions = new PollingConditions(timeout: 10)
        conditions.eventually {
            verifyPost("/pricefx/test-partition/loaddata/P", 1,
                    expectedResponse("requests/load-products-request.json"))
        }

    }

}

How to Test a Bean

Groovy
class BeansGitTest extends IntegrationTestSpecification {

    @Autowired
    private CamelContext context

    def "should deploy specific route and beans and process it"() {

        given:
        seedEntities([beanPrn("example-beans.xml"),routePrn("test-route5.xml")])

        when:
        Exchange exchange = sendBody("direct:test-route5", "Michal")

        then:
        context.getEndpoint("mock:end").receivedExchanges.size() == 1
        exchange.getIn().getBody() == "[foo, bar]"

    }

}

How to Test a Class

Groovy
class ClassesGitTest extends IntegrationTestSpecification {

    @Autowired
    private CamelContext context

    def "should deploy all classes and routes except two and process it"() {

        given:
        seedEntities([], [routePrn("import-csv-from-ftp-to-pricefx.xml"), routePrn("load-products-route.xml")])

        when:
        Exchange exchange = sendBody("direct:test-route3", "John")
        Exchange exchange2 = sendBody("direct:test-route4", "Hello")

        then:
        context.getEndpoint("mock:end").receivedExchanges.size() == 2
        exchange.getIn().getBody() == "Hello John"
        exchange2.getIn().getBody() == "Hello"

    }

    def "should deploy specific route and classes and process it"() {

        given:
        seedEntities([classPrn("ExampleProcessor.groovy"),routePrn("test-route3.xml")])

        when:
        Exchange exchange = sendBody("direct:test-route3", "Michal")

        then:
        context.getEndpoint("mock:end").receivedExchanges.size() == 1
        exchange.getIn().getBody() == "Hello Michal"

    }

}

How to Test a Mapper

Groovy
class MapperGitTest extends IntegrationTestSpecification {


    def "should deploy specific mapper and route and process it"() {

        given:
        seedEntities([routePrn("test-route.xml"), mapperPrn("test-mapper.xml")])

        when:
        Exchange exchange = sendBody("direct:test-route", [["test":"Jerry", "age": "42"]])

        then:
        exchange.getIn().getBody() == [["convertedAge": 42, "attribute1": "Jerry"]]

    }

}

How to Test a Route Consuming Data from SFTP

We have implemented the SftpTestSpecification class for handling SFTP processing. This class, during the test startup, initiates an SFTP server and establishes a connection named default-sftp-connection. The primary purpose of introducing the SftpTestSpecification is to simplify and standardize the testing process for SFTP routes.

Groovy
class SftpRouteGitTest extends SftpTestSpecification {

    def "load csv file from sftp"() {

        given:
        mockPost('/pricefx/test-partition/loaddata/P')

        seedEntities([routePrn("import-csv-from-ftp-to-pricefx.xml"), mapperPrn("pfx_import-csv-from-ftp-to-pricefx.import.csv.from.ftp.mapper.xml")])

        when:
        seedSftpFile("data/products/products2.csv", "file.csv", "file.done")

        then: 'Content from SFTP server is expected'
        def conditions = new PollingConditions(timeout: 10)
        conditions.eventually {
            MockEndpoint endpoint = context.getEndpoint("mock:end")
            assert endpoint.getExchanges().size() > 0
            verifyPost("/pricefx/test-partition/loaddata/P", 1, expectedResponse("requests/load-products-from-sftp-request.json"))
        }

    }

}

How to Use Properties

All properties from application.properties are loaded during a test startup. If you want to override a property or define a new one, you can do it by the seedProperty method.

Groovy
seedProperty("newProperty", "value")

How to Use seedEntities

The seedEntities method has two parameters – includeOnly and exclude. The includeOnly parameter is used to specify which entities should be included in the test, exclude is used to specify which entities should be excluded from the test.

seedEntities() loads configuration directly from the project's FileSystemConfigurationRepositorythe same files used in production. This is intentional: it tests real mappers and routes, not copies.

Groovy
// this code will load all entities from the repo directory
seedEntities()
Groovy
// this code will load only beans inside repo/beans/example-beans.xml and repo/routes/test-route5.xml
seedEntities([beanPrn("example-beans.xml"),routePrn("test-route5.xml")])
Groovy
// this code will load all entities except a route inside repo/routes/test-route5.xml
seedEntities([],[routePrn("import-csv-from-ftp-to-pricefx.xml")])

Limitation of seedFile(): seedFile(path, content) only supports flat paths — no subdirectory separators. Files must be placed directly in the category root (e.g., mappers/my-mapper.xml, not mappers/sub/my-mapper.xml). For subdirectory structures, use seedEntities() instead.


Testing Async Routes with PollingConditions

For routes triggered by a scheduler or file consumer, use Spock's PollingConditions to wait for async processing. Never use Thread.sleep().

Groovy
import spock.util.concurrent.PollingConditions

def conditions = new PollingConditions(timeout: 10, delay: 0.5)

then:
conditions.eventually {
    // assert the expected side effect (e.g., WireMock received a request)
    verifyPost("/pricefx/test-partition/loaddata/P", 1, expectedResponse("requests/expected.json"))
}

PollingConditions.eventually {} retries the block until it passes or the timeout expires.


Key Conventions

  1. Test file naming: Spec classes end with Spec (for Surefire) or Test (for both Surefire and Failsafe).

  2. WireMock partition: The default stubbed partition is test-partition.

  3. Clean state: Base classes reset the WireMock server and Camel context between tests. Avoid relying on state from a previous test method.

  4. Repository-backed tests: Place test configuration files under src/test/resources/repo/ when using IntegrationTestSpecification. For integration tests using real production config, use seedEntities() directly.

  5. Use @Unroll for parameterized tests: Produces a separate test report entry per where row.

  6. Prefer direct:start endpoints: Replace timer or other consumers with direct:start using AdviceWith.

  7. Use PollingConditions for async routes: Never sleep — use PollingConditions.eventually {} for side effects from timer or file consumers.