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.
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
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
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
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.
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.
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 FileSystemConfigurationRepository — the same files used in production. This is intentional: it tests real mappers and routes, not copies.
// this code will load all entities from the repo directory
seedEntities()
// 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")])
// 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().
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
-
Test file naming: Spec classes end with
Spec(for Surefire) orTest(for both Surefire and Failsafe). -
WireMock partition: The default stubbed partition is
test-partition. -
Clean state: Base classes reset the WireMock server and Camel context between tests. Avoid relying on state from a previous test method.
-
Repository-backed tests: Place test configuration files under
src/test/resources/repo/when usingIntegrationTestSpecification. For integration tests using real production config, useseedEntities()directly. -
Use
@Unrollfor parameterized tests: Produces a separate test report entry perwhererow. -
Prefer
direct:startendpoints: Replace timer or other consumers withdirect:startusingAdviceWith. -
Use
PollingConditionsfor async routes: Never sleep — usePollingConditions.eventually {}for side effects from timer or file consumers.