Reputation: 903
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
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