Testing (Beta)

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 @ContextConfiguration

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

WireMockSpecification (test.wiremock)

integration-test

Tests that only need an HTTP mock server and no Camel/Spring context

UnitTestSpecification

integration-test

Tests needing a Spring-managed CamelContext, property seeding, and connection/route/mapper/bean deployment

IntegrationTestSpecification

integration-test

Tests that load configuration from a FileSystemConfigurationRepository (i.e., a local repo/ directory)

SftpTestSpecification

integration-test

Tests that additionally require an SFTP server (via Testcontainers)

WireMockSpecification (pricefx-integration)

pricefx-integration

Component-level tests using @CamelTest with a standalone DefaultCamelContext and manual registry binding


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.

Groovy
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:

  • @ContextConfiguration and @TestPropertySource annotations pre-configured

  • A Spring-managed CamelContext (autowired)

  • @TempDir for 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() and cleanup()

What IntegrationTestSpecification adds:

  • A FileSystemConfigurationRepository loaded from src/test/resources/repo or src/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.

Groovy
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 DefaultCamelContext with a DefaultRegistry, injected into the spec as context, registry, and producer

  • route(xml) -- deploy a route definition from an XML string

  • mapper(xml) -- deploy a mapper definition from an XML string

  • mock(uri) -- get a MockEndpoint by 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:

Groovy
@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:

Groovy
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:

Groovy
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:

Groovy
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:

Groovy
then:
1 * priceFxClient.getDatamartApi().datamartGetfcsFCTypeTypedIdOrSourceName("uniqueName", _ as FetchRequest) >> { arguments ->
    request = arguments[1]
    response
}

request.startRow == 2
request.endRow == 3

Running Tests

Unit Tests (default)

Bash
mvn test
mvn test -pl pricefx-integration
mvn test -pl pricefx-integration -Dtest="PfxApiProducerFetchSpec"

Integration Tests

Bash
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:

Bash
export JAVA_HOME=/opt/homebrew/Cellar/openjdk@17/17.0.14/libexec/openjdk.jdk/Contents/Home
mvn test

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 in the integration-test module and mvich in the pricefx-integration module.

  3. Clean state: Both 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.

  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.