Fixing the way queue binding key gets used for rabbit listener
without this change we're very strict in terms of the number of called send methods on the rabbittemplate with this change we accept that the method could have been called more than once fixes #332
This commit is contained in:
@@ -666,7 +666,7 @@ package com.example.fraud;
|
||||
|
||||
import org.junit.Before;
|
||||
|
||||
import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc;
|
||||
import io.restassured.module.mockmvc.RestAssuredMockMvc;
|
||||
|
||||
public class FraudBase {
|
||||
@Before
|
||||
@@ -1109,15 +1109,15 @@ Example of a `pom.xml` inside the `server` folder.
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>1.5.4.BUILD-SNAPSHOT</version>
|
||||
<version>1.5.4.RELEASE</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
<spring-cloud-contract.version>1.1.2.BUILD-SNAPSHOT</spring-cloud-contract.version>
|
||||
<spring-cloud-dependencies.version>Dalston.BUILD-SNAPSHOT</spring-cloud-dependencies.version>
|
||||
<spring-cloud-contract.version>1.2.0.BUILD-SNAPSHOT</spring-cloud-contract.version>
|
||||
<spring-cloud-dependencies.version>Edgware.BUILD-SNAPSHOT</spring-cloud-dependencies.version>
|
||||
<excludeBuildFolders>true</excludeBuildFolders>
|
||||
</properties>
|
||||
|
||||
|
||||
@@ -171,10 +171,10 @@ class JUnitMessagingMethodBodyBuilder extends MessagingMethodBodyBuilder {
|
||||
request = "${request}\n\t\t\t\t\"${StringEscapeUtils.escapeJava(bodyAsString)}\"\n"
|
||||
}
|
||||
if (inputMessage.messageHeaders) {
|
||||
request = "${request}\t\t\t\t, headers()\n"
|
||||
request = "${request}\t\t\t\t, headers()"
|
||||
}
|
||||
inputMessage.messageHeaders?.executeForEachHeader { Header header ->
|
||||
request = "${request}\t\t\t\t\t\t${getHeaderString(header)}"
|
||||
request = "${request}\n\t\t\t\t\t\t${getHeaderString(header)}"
|
||||
}
|
||||
return "${request}\n\t\t\t)"
|
||||
}
|
||||
|
||||
@@ -16,13 +16,6 @@
|
||||
|
||||
package org.springframework.cloud.contract.verifier.messaging.amqp;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mockingDetails;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.springframework.amqp.support.converter.DefaultClassMapper.DEFAULT_CLASSID_FIELD_NAME;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -40,6 +33,14 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.contract.verifier.messaging.MessageVerifier;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.mockingDetails;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.springframework.amqp.support.converter.DefaultClassMapper.DEFAULT_CLASSID_FIELD_NAME;
|
||||
|
||||
/**
|
||||
* {@link MessageVerifier} implementation to integrate with plain spring-amqp/spring-rabbit.
|
||||
* It is meant to be used without interacting with a running bus.
|
||||
@@ -99,7 +100,7 @@ public class SpringAmqpStubMessages implements
|
||||
@Override
|
||||
public Message receive(String destination, long timeout, TimeUnit timeUnit) {
|
||||
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
|
||||
verify(this.rabbitTemplate).send(eq(destination), anyString(), messageCaptor.capture(), any(CorrelationData.class));
|
||||
verify(this.rabbitTemplate, atLeastOnce()).send(eq(destination), anyString(), messageCaptor.capture(), any(CorrelationData.class));
|
||||
|
||||
if (messageCaptor.getAllValues().isEmpty()) {
|
||||
log.info("no messages found on destination {}", destination);
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.example;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.amqp.rabbit.annotation.Exchange;
|
||||
import org.springframework.amqp.rabbit.annotation.Queue;
|
||||
import org.springframework.amqp.rabbit.annotation.QueueBinding;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.handler.annotation.Headers;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class RabbitManager {
|
||||
|
||||
public static final Logger LOG = LoggerFactory.getLogger(RabbitManager.class);
|
||||
|
||||
|
||||
private BookService service;
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Autowired
|
||||
public RabbitManager(BookService service, RabbitTemplate rabbitTemplate) {
|
||||
this.service = service;
|
||||
this.rabbitTemplate = rabbitTemplate;
|
||||
}
|
||||
|
||||
@RabbitListener(bindings = @QueueBinding(
|
||||
value = @Queue(),
|
||||
exchange = @Exchange(
|
||||
value = "input",
|
||||
durable="true",
|
||||
autoDelete="false",
|
||||
type="topic"),
|
||||
key = "event"
|
||||
))
|
||||
public void newBook(Book book, @Headers Map<String, String> headers) {
|
||||
LOG.info("Received new book with bookname = " + book.getName());
|
||||
LOG.info("Headers = " + headers);
|
||||
service.sendBook(book, headers.get("amqp_replyTo"));
|
||||
}
|
||||
|
||||
@RabbitListener(bindings = @QueueBinding(
|
||||
value = @Queue(),
|
||||
exchange = @Exchange(
|
||||
value = "input",
|
||||
durable="true",
|
||||
autoDelete="false",
|
||||
type="topic"),
|
||||
key = "event2"
|
||||
))
|
||||
public void newBook2(Book book, @Headers Map<String, String> headers) {
|
||||
LOG.info("newBook2 Received new book with bookname = " + book.getName());
|
||||
LOG.info("newBook2 Headers = " + headers);
|
||||
service.sendBook(book, headers.get("amqp_replyTo"));
|
||||
}
|
||||
}
|
||||
|
||||
interface BookService {
|
||||
void sendBook(Book book, String replyTo);
|
||||
|
||||
void newBook(Book book);
|
||||
|
||||
Book getBook(int index);
|
||||
|
||||
int noOfBooks();
|
||||
|
||||
List<Book> getBooks();
|
||||
|
||||
}
|
||||
|
||||
@Component
|
||||
class BookServiceImpl implements BookService {
|
||||
public static final Logger LOG = LoggerFactory.getLogger(BookServiceImpl.class);
|
||||
|
||||
private List<Book> books;
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Autowired
|
||||
public BookServiceImpl(RabbitTemplate rabbitTemplate) {
|
||||
books = new LinkedList<>();
|
||||
this.rabbitTemplate = rabbitTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendBook(Book book, String replyTo) {
|
||||
LOG.info("Received new book with bookname = " + book.getName());
|
||||
newBook(book);
|
||||
rabbitTemplate.convertAndSend("", replyTo, book);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void newBook(Book book) {
|
||||
books.add(book);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Book getBook(int index) {
|
||||
return books.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int noOfBooks() {
|
||||
return books.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Book> getBooks() {
|
||||
return books;
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ 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.ContractVerifierMessage
|
||||
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessaging
|
||||
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
@@ -33,6 +34,9 @@ import spock.lang.Specification
|
||||
|
||||
import javax.inject.Inject
|
||||
|
||||
import static com.toomuchcoding.jsonassert.JsonAssertion.assertThatJson
|
||||
import static org.assertj.core.api.Assertions.assertThat
|
||||
import static org.springframework.cloud.contract.verifier.messaging.util.ContractVerifierMessagingUtil.headers
|
||||
// Context configuration would end up in base class
|
||||
@ContextConfiguration(classes = [AmqpMessagingApplication], loader = SpringBootContextLoader)
|
||||
@AutoConfigureMessageVerifier
|
||||
@@ -79,6 +83,69 @@ class AmqpMessagingApplicationSpec extends Specification {
|
||||
JsonAssertion.assertThat(parsedJson).field('name').isEqualTo('some')
|
||||
}
|
||||
|
||||
@Issue("332")
|
||||
def "should work for second scenario"() {
|
||||
given:
|
||||
def dsl =
|
||||
Contract.make {
|
||||
description("""
|
||||
Represents scenario 2 from documentation:
|
||||
http://cloud.spring.io/spring-cloud-contract/spring-cloud-contract.html#_publisher_side_test_generation
|
||||
|
||||
"The input message triggers an output message."
|
||||
|
||||
```
|
||||
given:
|
||||
rabbit service is running
|
||||
when:
|
||||
input message is received
|
||||
then:
|
||||
message is send
|
||||
```
|
||||
|
||||
""")
|
||||
label 'some_label2'
|
||||
input {
|
||||
messageFrom('input')
|
||||
messageBody([
|
||||
name: 'foo2'
|
||||
])
|
||||
messageHeaders {
|
||||
messagingContentType(applicationJson())
|
||||
header('amqp_replyTo', 'amq.rabbitmq.reply-to')
|
||||
header('bill', 'bill')
|
||||
}
|
||||
}
|
||||
|
||||
outputMessage {
|
||||
sentTo('')
|
||||
body('''{ "name" : "foo2" }''')
|
||||
headers {
|
||||
messagingContentType(applicationJson())
|
||||
}
|
||||
}
|
||||
}
|
||||
// generated test should look like this:
|
||||
and:
|
||||
ContractVerifierMessage inputMessage = contractVerifierMessaging.create(
|
||||
"{\"name\":\"foo2\"}"
|
||||
, headers()
|
||||
.header("contentType", "application/json")
|
||||
.header("amqp_replyTo", "amq.rabbitmq.reply-to")
|
||||
.header("bill", "bill")
|
||||
)
|
||||
when:
|
||||
contractVerifierMessaging.send(inputMessage, "input")
|
||||
then:
|
||||
ContractVerifierMessage response = contractVerifierMessaging.receive("")
|
||||
assertThat(response).isNotNull()
|
||||
assertThat(response.getHeader("contentType")).isNotNull()
|
||||
assertThat(response.getHeader("contentType").toString()).isEqualTo("application/json")
|
||||
and:
|
||||
DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.getPayload()))
|
||||
assertThatJson(parsedJson).field("['name']").isEqualTo("foo2")
|
||||
}
|
||||
|
||||
@Issue("178")
|
||||
def "should work for input/output when bytes are used"() {
|
||||
given:
|
||||
|
||||
Reference in New Issue
Block a user