Testing Apache Camel servlet with spring cloud contract

huangapple go评论81阅读模式
英文:

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(&quot;/customers-controller/&quot;)
.get(&quot;/customers&quot;).to(&quot;direct:getcustomer)
.get(&quot;/{id}&quot;).to(&quot;direct:customerDetail&quot;)
.get(&quot;/{id}/orders&quot;).to(&quot;direct:customerOrders&quot;)
.post(&quot;/neworder&quot;).to(&quot;direct:customerNewOrder&quot;);
}

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(&quot;positive&quot;)
    input {
        messageFrom(&quot;seda:getcustomer&quot;)
        messageBody([
                id: &quot;25_body&quot;
        ])
        messageHeaders {
            messagingContentType(applicationJson())
//            header(&quot;id&quot;,&quot;123_header&quot;)
        }
    }
    outputMessage {
        sentTo(&quot;seda:iris-address-int&quot;)
        body([
                        &quot;id&quot;:&quot;25_body&quot;,&quot;firstname&quot;:null,&quot;lastname&quot;:null,&quot;email&quot;:null,&quot;phone&quot;:null,&quot;street&quot;:null,&quot;city&quot;:null,&quot;postcode&quot;: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 = &quot;http://localhost:&quot;  + randomServerPort + &quot;/customers-controller/customers&quot;;
        URI uri = new URI(baseUrl);
       
        ResponseEntity&lt;String&gt; 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 &quot;rabbitmq:localhost/person?queue=person&quot;; }
String finish() {
return &quot;rabbitmq:localhost/verifications?queue=verifications&quot;;
}
}

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

Contract.make {
label(&quot;positive&quot;)
input {
messageFrom(&quot;seda:person&quot;)
messageBody([
age: 25
])
messageHeaders {
messagingContentType(applicationJson())
}
}
outputMessage {
sentTo(&quot;seda:verifications&quot;)
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 &quot;seda:person&quot;;
}
@Override
String finish() {
return &quot;seda:verifications&quot;;
}
}
}

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 = &quot;com.example:beer-api-producer-camel&quot;,
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 -&gt; seda:person
// 	producers -&gt; seda:person -&gt; person -&gt; verifications -&gt; seda:verifications
// consumer -&gt; seda:verifications
@BeforeEach
public void setup() {
this.camelContext.getShutdownStrategy().setTimeout(1);
}
@Test
public void should_trigger_a_negative_verification() throws Exception {
this.producerTemplate.sendBodyAndHeader(&quot;seda:person&quot;, new Person(17),
&quot;contentType&quot;, &quot;application/json&quot;);
String string =
this.consumerTemplate.receiveBody(&quot;seda:verifications&quot;, 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(&quot;seda:person&quot;, new Person(25),
&quot;contentType&quot;, &quot;application/json&quot;);
String string =
this.consumerTemplate.receiveBody(&quot;seda:verifications&quot;, String.class);
Verification verification = this.objectMapper.readerFor(Verification.class).readValue(string);
BDDAssertions.then(verification).isNotNull();
BDDAssertions.then(verification.eligible).isTrue();
}
}

huangapple
  • 本文由 发表于 2020年8月6日 10:38:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/63276063.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定