Y. Leonce Eyog
Y. Leonce Eyog

Reputation: 903

Testing Apache Camel servlet with spring cloud contract

I have a spring boot application with routes defined as follows:

@Component
public class SampleRoute extends RouteBuilder {
 @Override
public void configure() throws Exception {

 rest("/customers-controller/")
.get("/customers").to("direct:getcustomer)
.get("/{id}").to("direct:customerDetail")
.get("/{id}/orders").to("direct:customerOrders")
.post("/neworder").to("direct:customerNewOrder");
}

I'm trying to create a contract to test this endpoint ('/customers') and create a stub that will be used in my consumer class.

A contract for messaging services like camel is similar to this:

    Contract.make {
    label("positive")
    input {
        messageFrom("seda:getcustomer")
        messageBody([
                id: "25_body"
        ])
        messageHeaders {
            messagingContentType(applicationJson())
//            header("id","123_header")
        }
    }
    outputMessage {
        sentTo("seda:iris-address-int")
        body([
                        "id":"25_body","firstname":null,"lastname":null,"email":null,"phone":null,"street":null,"city":null,"postcode":null
        ]
        )
        headers {
            messagingContentType()
        }
    }
}

Now, I'm not sure on how to define the contract such that it points to my chosen rest endpoint like I would do with a RestController.

Consider the test below. Is it possible to generate this test on the provider side using spring cloud contract given that I'm not using the @RestController but rather the rest component ?

@RunWith(CamelSpringBootRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)


public class TestRestRoute {

    @Autowired
    private TestRestTemplate restTemplate;
    @LocalServerPort
    int randomServerPort

 @Test
    public void test_bank_route() throws URISyntaxException, IOException {
        //call the REST API
       
        final String baseUrl = "http://localhost:"  + randomServerPort + "/customers-controller/customers";
        URI uri = new URI(baseUrl);
       
        ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class );
        Assert.assertEquals(MediaType.APPLICATION_JSON_VALUE, Objects.requireNonNull(response.getHeaders().getContentType()).toString());
        Assert.assertEquals(200, response.getStatusCodeValue());
        Assert.assertNull(response.getBody());
    }

Upvotes: 1

Views: 645

Answers (1)

Marcin Grzejszczak
Marcin Grzejszczak

Reputation: 11149

With this commit https://github.com/spring-cloud-samples/spring-cloud-contract-samples/commit/760d7105bde2f253ca0bff84fa3f4d5a35a1931e I've added a sample for Camel to spring cloud contract branch 3.0.x. Of course same rules apply to Spring Cloud Contract in other versions. In general what you can do is on the producer side:

Define a configuration for a route and extract the component URIs to separate methods:

@Configuration
class RouteConfiguration {

    @Bean
    RoutesBuilder myRouter() {
        return new RouteBuilder() {
            @Override
            public void configure() {
                from(start())
                        .bean(MyProcessor.class)
                        .to(finish());
            }
        };
    }

    // rabbitmq://hostname[:port]/exchangeName?[options]
    String start() { return "rabbitmq:localhost/person?queue=person"; }

    String finish() {
        return "rabbitmq:localhost/verifications?queue=verifications";
    }

}

However in the contract we will leverage the seda component (the way you did it)

Contract.make {
    label("positive")
    input {
        messageFrom("seda:person")
        messageBody([
                age: 25
        ])
        messageHeaders {
            messagingContentType(applicationJson())
        }
    }
    outputMessage {
        sentTo("seda:verifications")
        body([
                eligible: true
        ])
        headers {
            messagingContentType(applicationJson())
        }
    }
}

Now, in the base class for the generated tests we will change the configuration to reflect what we have in the contract

package com.example.demo;

import org.apache.camel.test.spring.CamelSpringRunner;
import org.junit.runner.RunWith;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.verifier.messaging.boot.AutoConfigureMessageVerifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;

@RunWith(CamelSpringRunner.class)
@SpringBootTest(classes = BaseClass.TestConfiguration.class)
// IMPORTANT
@AutoConfigureMessageVerifier
// IMPORTANT
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public abstract class BaseClass {

    @Configuration
    @EnableAutoConfiguration
    static class TestConfiguration extends RouteConfiguration {

        // was:     rabbit
        // will be: a queue
        @Override
        String start() {
            return "seda:person";
        }

        @Override
        String finish() {
            return "seda:verifications";
        }
    }
}

We're overriding the start() and finish() methods. You could do sth similar in your case. Then on the consumer side you can reference it like this:

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.camel.CamelContext;
import org.apache.camel.ConsumerTemplate;
import org.apache.camel.ProducerTemplate;
import org.assertj.core.api.BDDAssertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties;
import org.springframework.test.annotation.DirtiesContext;

@SpringBootTest
@AutoConfigureStubRunner(
        ids = "com.example:beer-api-producer-camel",
        stubsMode = StubRunnerProperties.StubsMode.LOCAL
)
// IMPORTANT
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class DemoApplicationTests {

    @Autowired ConsumerTemplate consumerTemplate;
    @Autowired ProducerTemplate producerTemplate;
    @Autowired CamelContext camelContext;
    ObjectMapper objectMapper = new ObjectMapper();

    // consumer -> seda:person
    //  producers -> seda:person -> person -> verifications -> seda:verifications
    // consumer -> seda:verifications

    @BeforeEach
    public void setup() {
        this.camelContext.getShutdownStrategy().setTimeout(1);
    }

    @Test
    public void should_trigger_a_negative_verification() throws Exception {
        this.producerTemplate.sendBodyAndHeader("seda:person", new Person(17),
                "contentType", "application/json");

        String string =
                this.consumerTemplate.receiveBody("seda:verifications", String.class);
        Verification verification = this.objectMapper.readerFor(Verification.class).readValue(string);
        BDDAssertions.then(verification).isNotNull();
        BDDAssertions.then(verification.eligible).isFalse();
    }

    @Test
    public void should_trigger_a_positive_verification() throws Exception {
        this.producerTemplate.sendBodyAndHeader("seda:person", new Person(25),
                "contentType", "application/json");

        String string =
                this.consumerTemplate.receiveBody("seda:verifications", String.class);
        Verification verification = this.objectMapper.readerFor(Verification.class).readValue(string);
        BDDAssertions.then(verification).isNotNull();
        BDDAssertions.then(verification.eligible).isTrue();
    }

}

Upvotes: 3

Related Questions