spring-cloud / spring-cloud-contract

Support for Consumer Driven Contracts in Spring
https://cloud.spring.io/spring-cloud-contract
Apache License 2.0
720 stars 439 forks source link

Dynamic properties in messageHeaders-section #709

Open srs-haw opened 6 years ago

srs-haw commented 6 years ago

I'd like to propose an enhancement. I have to use manual acknowledgement of messages in a Spring Cloud Stream scenario like this:

@StreamListener(MessagingChannelsChannelNames.INPUT)
    public void handle(@Payload int i,
                       @Header(AmqpHeaders.CHANNEL) Channel channel,
                       @Header(AmqpHeaders.DELIVERY_TAG) Long deliveryTag) throws IOException {
        // call some service-operations
        // ... and finally:
        channel.basicAck(deliveryTag, false);
    }

Therefore I have to pass a (mock)-Channel instance in my tests - by setting the message headers accordingly. For my own unit tests, this is not an issue.

Unfortunately I didn't find a way to do this in Spring Cloud Contract tests. A solution would be to allow dynamic properties in the messageHeaders-section like in this example this:

description: some description
label: accepted_verification2
input:
  messageFrom: eventsBookingserviceIn
  messageBody:
    "11"
  messageHeaders:
    amqp_channel: getMockChannel()  <---!!
    amqp_deliveryTag: 1
outputMessage:
    ...

getMockChannel() would a be function in my message test base class that just returns a mock(Channel.class) instance. But in Spring Cloud Contract this currently gets compiled to:

public void validate_testMessagingIn() throws Exception {
        // given:
            ContractVerifierMessage inputMessage = contractVerifierMessaging.create(
                "11"
                , headers()
                    .header("amqp_channel", "getMockChannel()") <---!! wrong
                    .header("amqp_deliveryTag", "1")
            );

        // when:
                ...
    }
marcingrzejszczak commented 6 years ago

If I understand correctly, you would like to verify that a method was executed on a channel?

Why not write a bean post processor that will wrap your channel with Mockito.spy(...)? That way you will be able to assert whatever you need.

srs-haw commented 6 years ago

No, the main problem is, that a (rabbitmq)Channel-instance musst be passed to the handle-method above somehow - otherwise an exception is thrown:

org.springframework.messaging.MessageHandlingException: Missing header 'amqp_channel' for 
method parameter type [interface com.rabbitmq.client.Channel], failedMessage=GenericMessage...

This instance must somehow be passed from contract tests. In my own tests I can do something like this:

 @Test
    public void messagingTest() throws Exception {
        messagingChannels.eventsBookingserviceIn()
                .send(MessageBuilder.withPayload(7)
                        .setHeader(AmqpHeaders.CHANNEL, mock(Channel.class))
                        .setHeader(AmqpHeaders.DELIVERY_TAG, 1)
                        .build());
...
marcingrzejszczak commented 6 years ago

Ahhh cause the header value must be an object...

AFAIR if you used Groovy to define the contract, you could be able to provide the pair of client / server via $(consumer("foo"), producer(execute("getMockChannel()"))). I think that would work. In YAML however we don't support such things to define the input message. That might be a missing feature.

Can you try to write the contract in Groovy in the way I mentioned to see if that "workaround" works for you?

srs-haw commented 6 years ago

Yes, workaround works! Thanks!

And yes, it would be great to have this in yaml!

marcingrzejszczak commented 6 years ago

Awesome! From my perspective, if there's a workaround, then at least you're not blocked and you can work normally :)

marcingrzejszczak commented 6 years ago

BTW can you confirm that it also works on the consumer side?

srs-haw commented 6 years ago

I'll check this soon...

srs-haw commented 6 years ago

Works!