Testing (Beta)
Test Framework Overview
The Integration Manager uses Spock Framework with Groovy as its testing foundation. Tests are written as Spock specifications (classes extending Specification) and use the given/when/then block structure.
Key dependencies:
|
Dependency |
Purpose |
|---|---|
|
Spock Framework |
BDD-style test framework for Groovy/Java |
|
WireMock |
HTTP mock server for simulating Pricefx API responses |
|
Apache Camel Test |
Camel context lifecycle and route testing utilities |
|
Spring Test |
Spring context bootstrapping via |
|
Testcontainers |
Container-based infrastructure (e.g., SFTP servers) |
All test files are Groovy (.groovy) and live under src/test/groovy/ in their respective modules.
Base Classes
The project provides a hierarchy of base specification classes. Choose the one that matches your test scope.
Class Hierarchy
Specification (Spock)
+-- WireMockSpecification (integration-test module)
+-- UnitTestSpecification (integration-test module, Spring-based)
+-- IntegrationTestSpecification (integration-test module, repo-backed)
+-- SftpTestSpecification (integration-test module, SFTP container)
There is also a separate WireMockSpecification inside pricefx-integration for component-level tests that use the @CamelTest annotation instead of Spring.
When to Use Each
|
Base Class |
Module |
Use Case |
|---|---|---|
|
|
|
Tests that only need an HTTP mock server and no Camel/Spring context |
|
|
|
Tests needing a Spring-managed |
|
|
|
Tests that load configuration from a |
|
|
|
Tests that additionally require an SFTP server (via Testcontainers) |
|
|
|
Component-level tests using |
Test Setup Approaches
There are two distinct approaches depending on which module you are writing tests in.
Approach 1: Spring-Based Tests (integration-test module)
Tests that extend UnitTestSpecification or IntegrationTestSpecification use Spring to manage the Camel context.
package net.pricefx.integration.test.definition
import net.pricefx.integration.test.UnitTestSpecification
import org.apache.camel.CamelContext
import org.apache.camel.Exchange
import org.apache.camel.builder.AdviceWith
import org.springframework.beans.factory.annotation.Autowired
class MapperTest extends UnitTestSpecification {
@Autowired
private CamelContext context
def "should test mapper"() {
given:
seedMapper('''<mappers>
<loadMapper id="test">
<body in="age" out="convertedAge" converter="stringToInteger"/>
<body in="test" out="attribute1"/>
</loadMapper>
</mappers>''')
seedRoute('''
<routes xmlns="http://camel.apache.org/schema/spring">
<route id="test-mapper">
<from uri="timer://foo?repeatCount=1"/>
<to uri="pfx-model:transform?mapper=test"/>
</route>
</routes>
''')
AdviceWith.adviceWith(context, "test-mapper", a ->
a.replaceFromWith("direct:start")
)
when:
Exchange exchange = sendBody("direct:start", [["test": "Jerry", "age": "42"]])
then:
exchange.getIn().getBody() == [["convertedAge": 42, "attribute1": "Jerry"]]
}
}
What UnitTestSpecification provides:
-
@ContextConfigurationand@TestPropertySourceannotations pre-configured -
A Spring-managed
CamelContext(autowired) -
@TempDirfor temporary file operations -
Convenience methods:
seedRoute(),seedMapper(),seedBean(),seedClass(),seedProperty(),seedDefaultConnection(),seedFile() -
sendBody(endpointUri, body)to dispatch an exchange -
expectedResponse(source)to load expected output from a file -
Automatic context cleanup in
setup()andcleanup()
What IntegrationTestSpecification adds:
-
A
FileSystemConfigurationRepositoryloaded fromsrc/test/resources/repoorsrc/main/resources/repo -
seedEntities()to bulk-load beans, classes, mappers, and routes from the repository with optional include/exclude filtering by PRN
Approach 2: @CamelTest Annotation (pricefx-integration module)
Component-level tests in pricefx-integration use the custom @CamelTest Spock extension, which manages a standalone DefaultCamelContext outside of Spring.
package net.pricefx.integration.component.api
import net.pricefx.integration.WireMockSpecification
import net.pricefx.integration.camel.CamelTest
@CamelTest
class PfxApiProducerFetchSpec extends WireMockSpecification {
Exchange exchange
PriceFxClient priceFxClient
def setup() {
cleanContext()
def registry = new DefaultRegistry()
registry.bind("filter", new Filter())
priceFxClient = Mock(PriceFxClient.class)
priceFxClient.generalApi >> Mock(GeneralDatasourceApiCompatible.class)
registry.bind("pricefx", new PriceFxConnection(client: priceFxClient))
exchange = new DefaultExchange(new DefaultCamelContext(registry))
pfxApiEndpoint = new PfxApiEndpoint('pfx-api', 'fetch', new PfxApiComponent())
}
def "fetch products"() {
given:
pfxApiEndpoint.pfxApiConfiguration = new PfxApiConfiguration(
objectType: ObjectType.P, filter: 'filter'
)
def pfxApiProducer = new PfxApiProducer(pfxApiEndpoint)
when:
pfxApiProducer.process(exchange)
then:
1 * priceFxClient.extendedProductApi.fetchproducts(_,_,_,_) >>
new FetchResponse(response: new FetchResponseData(data: []))
exchange.in.getHeader('totalRows') == 0
}
}
What @CamelTest provides (via CamelTestExtension):
-
A fresh
DefaultCamelContextwith aDefaultRegistry, injected into the spec ascontext,registry, andproducer -
route(xml)-- deploy a route definition from an XML string -
mapper(xml)-- deploy a mapper definition from an XML string -
mock(uri)-- get aMockEndpointby URI -
sendBody(),sendBodyAndHeader(),sendBodyAndHeaders(),send(),asyncSend()-- producer template delegates -
cleanContext()-- stop and remove all routes, reset mock endpoints
WireMock Patterns
WireMock is used to simulate the Pricefx API (and other HTTP endpoints) without requiring a live server.
How WireMock Is Initialized
Both WireMockSpecification classes use a shared singleton server on a dynamic port:
@Shared
def wireMockServer = WireMockSingleton.getInstance()
Automatic JWT Stub
Both WireMock base classes automatically stub the Pricefx JWT authentication endpoint in setup().
Stubbing API Responses
Use standard WireMock static imports:
import static com.github.tomakehurst.wiremock.client.WireMock.*
wireMockServer.stubFor(
post(urlEqualTo("/pricefx/mvich/datamart.getfcs/1234"))
.withRequestBody(containing('"sortBy":["sku"]'))
.willReturn(okJson('{"response":{"data":[],"status":0}}'))
)
Verifying Requests
The base class provides convenience methods:
verifyPost("/pricefx/mvich/some-endpoint", 2)
verifyPost("/pricefx/mvich/some-endpoint", 1, '{"expected":"json"}')
Lifecycle
WireMock is reset in setup(), cleanup(), setupSpec(), and cleanupSpec() via wireMockServer.resetAll(), so stubs do not leak between tests.
Mocking Pricefx Clients Directly
For component-level tests (using @CamelTest), you can mock the PriceFxClient and its sub-APIs using Spock mocks:
priceFxClient = Mock(PriceFxClient.class)
priceFxClient.generalApi >> Mock(GeneralDatasourceApiCompatible.class)
priceFxClient.lookuptableApi >> Mock(LookuptableApi.class)
priceFxClient.datamartApi >> Mock(DatamartApi.class)
registry.bind("pricefx", new PriceFxConnection(client: priceFxClient))
Then verify interactions in the then block:
then:
1 * priceFxClient.getDatamartApi().datamartGetfcsFCTypeTypedIdOrSourceName("uniqueName", _ as FetchRequest) >> { arguments ->
request = arguments[1]
response
}
request.startRow == 2
request.endRow == 3
Running Tests
Unit Tests (default)
mvn test
mvn test -pl pricefx-integration
mvn test -pl pricefx-integration -Dtest="PfxApiProducerFetchSpec"
Integration Tests
mvn verify -P integration-tests
mvn verify -P integration-camel-tests
The integration-tests profile sets spock.test.category=IntegrationTests and the integration-camel-tests profile sets spock.test.category=IntegrationCamelTests.
Java Version Requirement
Tests require Java 17. If your default mvn uses a different JDK, set JAVA_HOME before running:
export JAVA_HOME=/opt/homebrew/Cellar/openjdk@17/17.0.14/libexec/openjdk.jdk/Contents/Home
mvn test
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-partitionin theintegration-testmodule andmvichin thepricefx-integrationmodule. -
Clean state: Both 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. -
Use
@Unrollfor parameterized tests: Produces a separate test report entry perwhererow. -
Prefer
direct:startendpoints: Replace timer or other consumers withdirect:startusingAdviceWith.