使用 spring 云合约测试 Apache Camel servlet
Testing Apache Camel servlet with spring cloud contract
我有一个 spring 引导应用程序,其路由定义如下:
@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')并创建一个将在我的消费者中使用的存根 class。
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()
}
}
}
现在,我不确定如何定义合约,使其指向我选择的休息端点,就像我对 RestController 所做的那样。
考虑下面的测试。鉴于我没有使用 @RestController
而是 rest
组件,是否可以使用 spring 云合同在提供商端生成此测试?
@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());
}
通过此提交 https://github.com/spring-cloud-samples/spring-cloud-contract-samples/commit/760d7105bde2f253ca0bff84fa3f4d5a35a1931e 我已将 Camel 示例添加到 spring 云合同分支 3.0.x。当然,同样的规则也适用于其他版本的 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())
}
}
}
现在,在生成测试的基础 class 中,我们将更改配置以反映我们在合约中的内容
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";
}
}
}
我们正在覆盖 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
)
// 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();
}
}
我有一个 spring 引导应用程序,其路由定义如下:
@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')并创建一个将在我的消费者中使用的存根 class。
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()
}
}
}
现在,我不确定如何定义合约,使其指向我选择的休息端点,就像我对 RestController 所做的那样。
考虑下面的测试。鉴于我没有使用 @RestController
而是 rest
组件,是否可以使用 spring 云合同在提供商端生成此测试?
@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());
}
通过此提交 https://github.com/spring-cloud-samples/spring-cloud-contract-samples/commit/760d7105bde2f253ca0bff84fa3f4d5a35a1931e 我已将 Camel 示例添加到 spring 云合同分支 3.0.x。当然,同样的规则也适用于其他版本的 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())
}
}
}
现在,在生成测试的基础 class 中,我们将更改配置以反映我们在合约中的内容
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";
}
}
}
我们正在覆盖 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
)
// 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();
}
}