Merge branch '1.0.x'

fixes #178
This commit is contained in:
Marcin Grzejszczak
2016-12-27 12:26:13 +01:00
14 changed files with 181 additions and 7 deletions

View File

@@ -22,6 +22,11 @@ http://wiremock.org[WireMock] with different servers by using the
https://github.com/spring-cloud/spring-cloud-contract/tree/master/samples[samples]
for more details.
IMPORTANT: The Spring Cloud Release Train BOM imports `spring-cloud-contract-dependencies`
which in turn has exclusions for the dependencies needed by WireMock. This might lead to a situation that
even if you're not using Spring Cloud Contract then your dependencies will be influenced
anyways.
If you have a Spring Boot application that uses Tomcat as an embedded
server, for example (the default with `spring-boot-starter-web`), then
you can simply add `spring-cloud-contract-wiremock` to your classpath

View File

@@ -20,15 +20,18 @@ import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc
import groovy.json.JsonSlurper
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootContextLoader
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.cloud.contract.stubrunner.StubRunning
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.ContextConfiguration
import spock.lang.Specification
/**
* @author Marcin Grzejszczak
*/
// tag::boot_usage[]
@ContextConfiguration(classes = StubRunnerBoot, loader = SpringBootContextLoader)
@SpringBootTest(properties = "spring.cloud.zookeeper.enabled=false")
@ActiveProfiles("test")
class StubRunnerBootSpec extends Specification {

View File

@@ -21,6 +21,7 @@ import org.junit.BeforeClass
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.test.context.SpringBootContextLoader
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.cloud.contract.stubrunner.StubFinder
import org.springframework.cloud.contract.stubrunner.StubNotFoundException
import org.springframework.context.annotation.Configuration
@@ -36,6 +37,7 @@ import spock.lang.Specification
// Not necessary if Spring Cloud is used. TODO: make it work without this.
// tag::test[]
@ContextConfiguration(classes = Config, loader = SpringBootContextLoader)
@SpringBootTest(properties = [" stubrunner.cloud.enabled=false", "stubrunner.camel.enabled=false"])
@AutoConfigureStubRunner
@DirtiesContext
@ActiveProfiles("test")

View File

@@ -71,7 +71,6 @@ public class ContractVerifierAmqpAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MessageVerifier<Message> contractVerifierMessageExchange() {
return new SpringAmqpStubMessages(this.rabbitTemplate,
new MessageListenerAccessor(this.rabbitListenerEndpointRegistry, this.simpleMessageListenerContainers, this.bindings));
}

View File

@@ -14,7 +14,7 @@ import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
/**
* Abstraction hiding details of the different sources of message listeners
*
* Needed because {@link org.springframework.amqp.rabbit.annotation.RabbitListenerAnnotationBeanPostProcessor} adds the listners to the
* Needed because {@link org.springframework.amqp.rabbit.annotation.RabbitListenerAnnotationBeanPostProcessor} adds the listeners to the
* {@link RabbitListenerEndpointRegistry} so that the registry is empty when wired into an auto configuration class
* so we wrap it in the accessor to access the listeners late at runtime
*
@@ -38,7 +38,6 @@ class MessageListenerAccessor {
List<SimpleMessageListenerContainer> listenerContainers = collectListenerContainers();
//we interpret the destination as exchange name and collect all the queues bound to this exchange
Set<String> queueNames = collectQueuesBoundToDestination(destination);
return getListenersByBoundQueues(listenerContainers, queueNames);
}

View File

@@ -40,6 +40,8 @@ public class ContractVerifierObjectMapper {
public String writeValueAsString(Object payload) throws JsonProcessingException {
if (payload instanceof String) {
return payload.toString();
} else if (payload instanceof byte[]) {
return new String((byte[]) payload);
}
return this.objectMapper.writeValueAsString(payload);
}

View File

@@ -0,0 +1,30 @@
package org.springframework.cloud.contract.verifier.messaging.internal
import spock.lang.Specification
class ContractVerifierObjectMapperSpec extends Specification {
ContractVerifierObjectMapper mapper = new ContractVerifierObjectMapper()
def "should convert an object into a json representation"() {
given:
MyClass input = new MyClass(foo: "bar")
when:
String result = mapper.writeValueAsString(input)
then:
result == '''{"foo":"bar"}'''
}
def "should convert bytes into a json representation"() {
given:
String input = '''{"foo":"bar"}'''
when:
String result = mapper.writeValueAsString(input.bytes)
then:
result == '''{"foo":"bar"}'''
}
class MyClass {
String foo
}
}

View File

@@ -43,4 +43,10 @@ public class AmqpMessagingApplication {
public Exchange testExchange() {
return new TopicExchange("test-exchange");
}
@Bean
MessagePublisher messagePublisher(RabbitTemplate rabbitTemplate) {
return new MessagePublisher(rabbitTemplate, testExchange());
}
}

View File

@@ -0,0 +1,81 @@
package com.example;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author Marcin Grzejszczak
*/
@Configuration
class Issue178ListenerConfiguration {
@Bean
SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory, RabbitTemplate rabbitTemplate) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addQueueNames("rated-item-service.rated-item-event.exchange");
container.setMessageListener(exampleListener(rabbitTemplate));
return container;
}
@Bean
MessageListener exampleListener(final RabbitTemplate rabbitTemplate) {
return new MessageListener() {
public void onMessage(Message message) {
System.out.println("received: " + message);
try {
String payload = new ObjectMapper().writeValueAsString(new MyPojo("992e46d8-ab05-4a26-a740-6ef7b0daeab3", "CREATED"));
Message outputMessage = MessageBuilder.withBody(payload.getBytes()).build();
rabbitTemplate.send(issue178OutputExchange().getName(), "routingkey", outputMessage);
}
catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
};
}
static class MyPojo {
public String ratedItemId;
public String eventType;
public MyPojo(String ratedItemId, String eventType) {
this.ratedItemId = ratedItemId;
this.eventType = eventType;
}
public MyPojo() {
}
}
@Bean Queue issue178InputQueue() {
return new Queue("rated-item-service.rated-item-event.exchange", false);
}
@Bean
TopicExchange issue178InputExchange() {
return new TopicExchange("rated-item-service.rated-item-event.exchange");
}
@Bean
TopicExchange issue178OutputExchange() {
return new TopicExchange("bill-service.rated-item-event.retry-exchange");
}
@Bean Binding binding() {
return BindingBuilder.bind(issue178InputQueue()).to(issue178InputExchange()).with("rated-item-service.rated-item-event.exchange");
}
}

View File

@@ -2,16 +2,13 @@ package com.example;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MessagePublisher {
private final RabbitTemplate rabbitTemplate;
private final Exchange exchange;
@Autowired
public MessagePublisher(RabbitTemplate rabbitTemplate, Exchange exchange) {
this.rabbitTemplate = rabbitTemplate;
this.exchange = exchange;

View File

@@ -19,19 +19,24 @@ package com.example
import com.jayway.jsonpath.DocumentContext
import com.jayway.jsonpath.JsonPath
import com.toomuchcoding.jsonassert.JsonAssertion
import groovy.json.JsonOutput
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootContextLoader
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.verifier.messaging.boot.AutoConfigureMessageVerifier
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessaging
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper
import org.springframework.test.context.ContextConfiguration
import spock.lang.Issue
import spock.lang.Specification
import javax.inject.Inject
// Context configuration would end up in base class
@ContextConfiguration(classes = [AmqpMessagingApplication], loader = SpringBootContextLoader)
@AutoConfigureMessageVerifier
@SpringBootTest(properties = "stubrunner.amqp.enabled=true")
class AmqpMessagingApplicationSpec extends Specification {
// ALL CASES
@@ -74,6 +79,44 @@ class AmqpMessagingApplicationSpec extends Specification {
JsonAssertion.assertThat(parsedJson).field('name').isEqualTo('some')
}
@Issue("178")
def "should work for input/output when bytes are used"() {
given:
def inputBody = [
ratedItemId: "992e46d8-ab05-4a26-a740-6ef7b0daeab3",
eventType: "CREATED"
]
def dsl = Contract.make {
label 'ratedItem-no-metricid'
input {
messageFrom("rated-item-service.rated-item-event.exchange")
messageHeaders {
header("X-tenant", "1234")
header("contentType", "application/json")
}
messageBody(inputBody)
}
outputMessage {
sentTo('bill-service.rated-item-event.retry-exchange')
body(
ratedItemId: "992e46d8-ab05-4a26-a740-6ef7b0daeab3",
eventType: "CREATED"
)
}
}
when:
contractVerifierMessaging.send(contractVerifierMessaging.create(new JsonOutput().toJson(inputBody), [
"X-tenant": "1234",
"contentType": "application/json"
]), "rated-item-service.rated-item-event.exchange")
then:
def response = contractVerifierMessaging.receive('bill-service.rated-item-event.retry-exchange')
and:
DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.payload))
JsonAssertion.assertThat(parsedJson).field('ratedItemId').isEqualTo('992e46d8-ab05-4a26-a740-6ef7b0daeab3')
JsonAssertion.assertThat(parsedJson).field('eventType').isEqualTo('CREATED')
}
// BASE CLASS WOULD HAVE THIS:
@Autowired MessagePublisher messagePublisher

View File

@@ -21,6 +21,7 @@ import com.jayway.jsonpath.JsonPath
import com.toomuchcoding.jsonassert.JsonAssertion
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootContextLoader
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.verifier.messaging.MessageVerifier
import org.springframework.cloud.contract.verifier.messaging.boot.AutoConfigureMessageVerifier
@@ -37,6 +38,7 @@ import javax.inject.Inject
// Context configuration would end up in base class
@ContextConfiguration(classes = [StreamMessagingApplication], loader = SpringBootContextLoader)
@DirtiesContext
@SpringBootTest(properties = "debug=true")
@AutoConfigureMessageVerifier
public class StreamMessagingApplicationSpec extends Specification {

View File

@@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.test.context.SpringBootContextLoader
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.stubrunner.StubFinder
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner
@@ -40,6 +41,7 @@ import spock.lang.Specification
* @author Marcin Grzejszczak
*/
@ContextConfiguration(classes = Config, loader = SpringBootContextLoader)
@SpringBootTest(properties = "debug=true")
@AutoConfigureStubRunner
@IgnoreIf({ os.windows })
class CamelStubRunnerSpec extends Specification {

View File

@@ -21,6 +21,7 @@ import groovy.json.JsonSlurper
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.test.context.SpringBootContextLoader
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.stubrunner.StubFinder
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner
@@ -36,10 +37,12 @@ import spock.lang.IgnoreIf
import spock.lang.Specification
import java.util.concurrent.TimeUnit
/**
* @author Marcin Grzejszczak
*/
@ContextConfiguration(classes = Config, loader = SpringBootContextLoader)
@SpringBootTest(properties = "debug=true")
@AutoConfigureStubRunner
@AutoConfigureMessageVerifier
@IgnoreIf({ os.windows })