英文:
Testing Apache Camel servlet with spring cloud contract
问题
我有一个Spring Boot应用程序,路由定义如下:
@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");
}
}
我正在尝试创建一个合同来测试这个端点('/customers'),并创建一个在我的消费者类中使用的存根。
像Camel这样的消息服务的合同类似于这样:
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()
}
}
}
现在,我不确定如何定义合同,以便它指向我选择的REST端点,就像我在使用@RestController
时所做的那样。
考虑下面的测试。在提供者端使用Spring Cloud Contract是否可能生成这个测试,考虑到我没有使用@RestController
,而是使用了rest
组件?
@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 {
// 调用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());
}
}
英文:
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());
}
答案1
得分: 3
以下是您提供的内容的翻译:
对于此提交 https://github.com/spring-cloud-samples/spring-cloud-contract-samples/commit/760d7105bde2f253ca0bff84fa3f4d5a35a1931e ,我已经在spring cloud contract分支3.0.x中添加了一个Camel示例。当然,对于其他版本的Spring Cloud Contract也适用相同的规则。一般来说,在生产者端可以执行以下操作:
定义路由的配置并将组件URI提取到单独的方法中:
@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";
}
}
然而,在契约中,我们将利用seda
组件(您之前的方式)
Contract.make {
label("positive")
input {
messageFrom("seda:person")
messageBody([
age: 25
])
messageHeaders {
messagingContentType(applicationJson())
}
}
outputMessage {
sentTo("seda:verifications")
body([
eligible: true
])
headers {
messagingContentType(applicationJson())
}
}
}
现在,在生成测试的基类中,我们将更改配置以反映契约中的内容:
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)
@AutoConfigureMessageVerifier
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public abstract class BaseClass {
@Configuration
@EnableAutoConfiguration
static class TestConfiguration extends RouteConfiguration {
@Override
String start() {
return "seda:person";
}
@Override
String finish() {
return "seda:verifications";
}
}
}
我们正在重写start()
和finish()
方法。在您的情况下,您也可以执行类似的操作。然后,在消费者端,您可以像这样引用它:
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
)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class DemoApplicationTests {
@Autowired ConsumerTemplate consumerTemplate;
@Autowired ProducerTemplate producerTemplate;
@Autowired CamelContext camelContext;
ObjectMapper objectMapper = new ObjectMapper();
@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();
}
}
希望这些翻译对您有所帮助。
英文:
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();
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论