Added more docs for messaging

This commit is contained in:
Marcin Grzejszczak
2016-04-23 23:24:42 +02:00
parent 4c16055ea5
commit b89e5a45ad
10 changed files with 262 additions and 153 deletions

View File

@@ -9,6 +9,7 @@ class MessagingMethodBodyBuilderSpec extends Specification {
def "should work for triggered based messaging with Spock"() {
given:
// tag::trigger_method_dsl[]
def contractDsl = GroovyDsl.make {
label 'some_label'
input {
@@ -22,13 +23,16 @@ class MessagingMethodBodyBuilderSpec extends Specification {
}
}
}
// end::trigger_method_dsl[]
MethodBodyBuilder builder = new SpockMessagingMethodBodyBuilder(contractDsl)
BlockBuilder blockBuilder = new BlockBuilder(" ")
when:
builder.appendTo(blockBuilder)
def test = blockBuilder.toString()
then:
stripped(test) == stripped('''
stripped(test) == stripped(
// tag::trigger_method_test[]
'''
when:
bookReturnedTriggered()
@@ -38,7 +42,9 @@ class MessagingMethodBodyBuilderSpec extends Specification {
and:
DocumentContext parsedJson = JsonPath.parse(accurestObjectMapper.writeValueAsString(response.payload))
assertThatJson(parsedJson).field("bookName").isEqualTo("foo")
''')
'''
// end::trigger_method_test[]
)
}
def "should work for triggered based messaging with JUnit"() {
@@ -62,7 +68,9 @@ class MessagingMethodBodyBuilderSpec extends Specification {
builder.appendTo(blockBuilder)
def test = blockBuilder.toString()
then:
stripped(test) == stripped('''
stripped(test) == stripped(
// tag::trigger_method_junit_test[]
'''
// when:
bookReturnedTriggered();
@@ -72,7 +80,9 @@ class MessagingMethodBodyBuilderSpec extends Specification {
// and:
DocumentContext parsedJson = JsonPath.parse(accurestObjectMapper.writeValueAsString(response.getPayload()));
assertThatJson(parsedJson).field("bookName").isEqualTo("foo");
''')
'''
// end::trigger_method_junit_test[]
)
}
private String stripped(String text) {

View File

@@ -1,137 +0,0 @@
package io.codearte.accurest.builder
import io.codearte.accurest.dsl.GroovyDsl
import spock.lang.Specification
/**
# TO CONSIDER
- multiple messages can be sent out (for now let's focus on a single one)
- tests thanks to stream-test-binder will get executed in single threaded mode (no need for awaitility)
- do we need an explicit assertion of an input message (the one that enters the source?) or will
it be done in the processor
- let's support only JSON payload and headers taken from message ATM
# TRIGGERING MESSAGES
Triggering for the client side might be done via stub runner messaging module.
## JUNIT
@ClassRule public static AccurestRule stubFinder = new AccurestRule()
.repoRoot(repoRoot())
.downloadStub("io.codearte.accurest.stubs", "loanIssuance")
.downloadStub("io.codearte.accurest.stubs:fraudDetectionServer");
## SPRING
@Autowired StubFinder stubFinder
## TRIGGERING EXAMPLES
//test:
// execute all triggers for all artifacts
stubFinder.trigger()
// execute all triggers named 'some_label' for all artifacts
stubFinder.trigger("some_label")
// execute all triggers named 'some_label' for the artifact in Ivy notation
stubFinder.trigger("io.codearte.accurest.stubs:fraudDetectionServer", "some_label")
// execute all triggers named 'some_label' for the artifact with id ...
stubFinder.trigger("fraudDetectionServer", "some_label")
if no label is provided then all triggers will get executed
# CLIENT SIDE TEST EXAMPLE
## DSL
GroovyDsl.make {
label 'some_label'
input {
triggeredBy(execute('method()'))
}
outputMessage {
onChannel('messageFrom')
messageBody("book returned")
}
}
## TEST
@Test
public void run_some_test() {
// given
client.borrowABook();
// when - sends a message to the messageFrom provided in the "outputMessage" section of the DSL
stubFinder.trigger("client_returned_a_book");
// then
then(client).hasNoBooksBorrowed();
}
*
* @author Marcin Grzejszczak
*/
class MessagingSpec extends Specification {
// client side: must have a possibility to "trigger" sending of a message to the given messageFrom
// server side: will run the method and await upon receiving message on the output messageFrom
def "should generate tests triggered by a method"() {
expect:
GroovyDsl.make {
label 'some_label'
input {
triggeredBy('bookReturnedTriggered()')
}
outputMessage {
sentTo('channel')
body('''{ "bookName" : "foo" }''')
headers {
header('BOOK-NAME', 'foo')
}
}
}
}
// client side: if sends a message to input.messageFrom then message will be sent to output.messageFrom
// server side: will send a message to input, verify the message contents and await upon receiving message on the output messageFrom
def "should generate tests triggered by a message"() {
expect:
GroovyDsl.make {
input {
messageFrom('input')
messageBody("some message")
messageHeaders {
header('key', 'value')
}
}
outputMessage {
sentTo('output')
body('message')
headers {
header('anotherkey', 'anothervalue')
}
}
}
}
// client side: if sends a message to input.messageFrom then message if matches will get consumed
// server side: will send a message to input and verify the message contents
def "should generate tests without destination, triggered by a message"() {
expect:
GroovyDsl.make {
input {
messageFrom('input')
messageBody("some message")
messageHeaders {
header('key', 'value')
}
}
}
}
}

View File

@@ -578,6 +578,8 @@ Accurest will also generate tests with guaranteed order of execution.
include::stubrunner.adoc[]
include::stubrunner_msg.adoc[]
== Migration Guide
=== Migration to 0.4.7

View File

@@ -43,4 +43,10 @@ You have to provide as a dependency the `accurest-messaging-core` module. Exampl
[source,groovy,indent=0]
----
testCompile "io.codearte.accurest:accurest-messaging-core:${accurestVersion}"
----
----
=== Publisher side test generation
Having the `input` or `outputMessage` sections in your DSL will result in creation of tests on the publisher's side.
//TODO: add examples from MessaginMethodBodyBuilderSpec

View File

@@ -1,4 +1,4 @@
== Accurest REST
== Accurest HTTP
=== Gradle Project

View File

@@ -1,4 +1,4 @@
== Stub Runner
== Stub Runner for HTTP
One of the issues that you could have encountered while using Accurest was to pass the generated WireMock JSON stubs from the server side to the client side (or various clients).
The same takes place in terms of client side generation for messaging.

View File

@@ -0,0 +1,53 @@
== Stub Runner for Messaging
WARNING: Feature available since 1.0.7
Stub Runner has the functionality to run the published stubs in memory. It can integrate with the following frameworks out of the box
- Spring Integration
- Spring Cloud Stream
- Apache Camel
It also provides points of entry to integrate with any other solution on the market.
=== DSL
The DSL for messaging looks a little bit different than the one that focuses on HTTP.
==== Output triggered by a method
The output message can be triggered by calling a method (e.g. a Scheduler was started and a message was sent)
[source,groovy]
----
include::../../../../samples/messaging-integration/src/test/groovy/io/codearte/accurest/samples/messaging/IntegrationMessagingApplicationSpec.groovy[tags=method_trigger,indent=0]
----
In this case the output message will be sent to `output` if a method called `bookReturnedTriggered` will be executed. In the message *publisher's* side
we will generate a test that will call that method to trigger the message. On the *consumer* side you can use the `some_label` to trigger the message.
==== Output triggered by a message
The output message can be triggered by receiving a message.
[source,groovy]
----
include::../../../../samples/messaging-integration/src/test/groovy/io/codearte/accurest/samples/messaging/IntegrationMessagingApplicationSpec.groovy[tags=message_trigger,indent=0]
----
In this case the output message will be sent to `output` if a proper message will be received on the `input` destination. In the message *publisher's* side
we will generate a test that will send the input message to the defined destination. On the *consumer* side you can either send a message to the input
destination or use the `some_label` to trigger the message.
=== Stub triggering
To trigger a message it's enough to use the `StubTiggerer` interface:
[source,groovy]
----
include::../../stub-runner/src/main/groovy/io/codearte/accurest/stubrunner/StubTriggerer.groovy[]
----
For convenience the `StubFinder` interface extends `StubTrigger` so it's enough to use only one in your tests.
include::../../../../stub-runner/stub-runner-messaging/stub-runner-messaging-camel/README.adoc[]

View File

@@ -14,9 +14,6 @@ import spock.lang.Specification
import javax.inject.Inject
/**
* SPIKE ON TESTS FROM NOTES IN MessagingSpec
*/
// Context configuration would end up in base class
@ContextConfiguration(classes = [IntegrationMessagingApplication], loader = SpringApplicationContextLoader)
public class IntegrationMessagingApplicationSpec extends Specification {
@@ -27,19 +24,30 @@ public class IntegrationMessagingApplicationSpec extends Specification {
def "should work for triggered based messaging"() {
given:
// tag::method_trigger[]
def dsl = GroovyDsl.make {
// Human readable description
description 'Some description'
// Label by means of which the output message can be triggered
label 'some_label'
// input to the contract
input {
// the contract will be triggered by a method
triggeredBy('bookReturnedTriggered()')
}
// output message of the contract
outputMessage {
// destination to which the output message will be sent
sentTo('output')
// the body of the output message
body('''{ "bookName" : "foo" }''')
// the headers of the output message
headers {
header('BOOK-NAME', 'foo')
}
}
}
// end::method_trigger[]
// generated test should look like this:
when:
bookReturnedTriggered()
@@ -53,13 +61,19 @@ public class IntegrationMessagingApplicationSpec extends Specification {
def "should generate tests triggered by a message"() {
given:
// tag::message_trigger[]
def dsl = GroovyDsl.make {
description 'Some Description'
label 'some_label'
// input is a message
input {
// the message was received from this destination
messageFrom('input')
// has the following body
messageBody([
bookName: 'foo'
])
// and the following headers
messageHeaders {
header('sample', 'header')
}
@@ -74,6 +88,7 @@ public class IntegrationMessagingApplicationSpec extends Specification {
}
}
}
// end::message_trigger[]
// generated test should look like this:

View File

@@ -0,0 +1,105 @@
:input_name: jms:input
:output_name: jms:output
=== Stub Runner Messaging Camel
Accurest Stub Runner's messaging module gives you an easy way to integrate with Apache Camel.
For the provided artifacts it will automatically download the stubs and register the required
routes.
==== Examples
===== Stubs structure
Let us assume that we have the following Maven repository with a deployed stubs for the
`camelService` application.
[source,bash,indent=0]
----
└── .m2
└── repository
└── io
└── codearte
└── accurest
└── stubs
└── camelService
├── 0.0.1-SNAPSHOT
│   ├── camelService-0.0.1-SNAPSHOT.pom
│   ├── camelService-0.0.1-SNAPSHOT-stubs.jar
│   └── maven-metadata-local.xml
└── maven-metadata-local.xml
----
And the stubs contain the following structure:
[source,bash,indent=0]
----
├── META-INF
│   └── MANIFEST.MF
└── repository
├── accurest
│   ├── bookDeleted.groovy
│   ├── bookReturned1.groovy
│   └── bookReturned2.groovy
└── mappings
----
Let's consider the following contracts (let' number it with *1*):
[source,groovy]
----
include::src/test/groovy/io/codearte/accurest/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=sample_dsl,indent=0]
----
and number *2*
[source,groovy]
----
include::src/test/groovy/io/codearte/accurest/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=sample_dsl_2,indent=0]
----
===== Scenario 1 (no input message)
So as to trigger a message via the `return_book_1` label we'll use the `StubTiggerer` interface as follows
[source,groovy]
----
include::src/test/groovy/io/codearte/accurest/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=client_trigger,indent=0]
----
Next we'll want to listen to the output of the message sent to `{output_name}`
[source,groovy]
----
include::src/test/groovy/io/codearte/accurest/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=client_trigger_receive,indent=0]
----
And the received message would pass the following assertions
[source,groovy]
----
include::src/test/groovy/io/codearte/accurest/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=client_trigger_message,indent=0]
----
===== Scenario 2 (output triggered by input)
Since the route is set for you it's enough to just send a message to the `{output_name}` destination.
[source,groovy]
----
include::src/test/groovy/io/codearte/accurest/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=client_send,indent=0]
----
Next we'll want to listen to the output of the message sent to `{output_name}`
[source,groovy]
----
include::src/test/groovy/io/codearte/accurest/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=client_receive,indent=0]
----
And the received message would pass the following assertions
[source,groovy]
----
include::src/test/groovy/io/codearte/accurest/stubrunner/messaging/camel/CamelStubRunnerSpec.groovy[tags=client_receive_message,indent=0]
----

View File

@@ -2,6 +2,7 @@ package io.codearte.accurest.stubrunner.messaging.camel
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import io.codearte.accurest.dsl.GroovyDsl
import io.codearte.accurest.stubrunner.StubFinder
import org.apache.activemq.camel.component.ActiveMQComponent
import org.apache.camel.CamelContext
@@ -29,24 +30,36 @@ class CamelStubRunnerSpec extends Specification {
def 'should download the stub and register a route for it'() {
when:
// tag::client_send[]
camelContext.createProducerTemplate().sendBodyAndHeaders('jms:input', new BookReturned('foo'), [sample: 'header'])
// end::client_send[]
then:
// tag::client_receive[]
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
// end::client_receive[]
and:
// tag::client_receive_message[]
receivedMessage != null
assertJsons(receivedMessage.in.body)
assertThatBodyContainsBookNameFoo(receivedMessage.in.body)
receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
// end::client_receive_message[]
}
def 'should trigger a message by label'() {
when:
// tag::client_trigger[]
stubFinder.trigger('return_book_1')
// end::client_trigger[]
then:
// tag::client_trigger_receive[]
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
// end::client_trigger_receive[]
and:
// tag::client_trigger_message[]
receivedMessage != null
assertJsons(receivedMessage.in.body)
assertThatBodyContainsBookNameFoo(receivedMessage.in.body)
receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
// end::client_trigger_message[]
}
def 'should trigger a label for the existing groupId:artifactId'() {
@@ -56,7 +69,7 @@ class CamelStubRunnerSpec extends Specification {
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
and:
receivedMessage != null
assertJsons(receivedMessage.in.body)
assertThatBodyContainsBookNameFoo(receivedMessage.in.body)
receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
}
@@ -67,7 +80,7 @@ class CamelStubRunnerSpec extends Specification {
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
and:
receivedMessage != null
assertJsons(receivedMessage.in.body)
assertThatBodyContainsBookNameFoo(receivedMessage.in.body)
receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
}
@@ -96,11 +109,11 @@ class CamelStubRunnerSpec extends Specification {
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
and:
receivedMessage != null
assertJsons(receivedMessage.in.body)
assertThatBodyContainsBookNameFoo(receivedMessage.in.body)
receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
}
private boolean assertJsons(Object payload) {
private boolean assertThatBodyContainsBookNameFoo(Object payload) {
String objectAsString = payload instanceof String ? payload :
JsonOutput.toJson(payload)
def json = new JsonSlurper().parseText(objectAsString)
@@ -111,4 +124,46 @@ class CamelStubRunnerSpec extends Specification {
ActiveMQComponent activeMQComponent(@Value('${activemq.url:vm://localhost?broker.persistent=false}') String url) {
return new ActiveMQComponent(brokerURL: url)
}
GroovyDsl dsl =
// tag::sample_dsl[]
io.codearte.accurest.dsl.GroovyDsl.make {
label 'return_book_1'
input {
triggeredBy('bookReturnedTriggered()')
}
outputMessage {
sentTo('jms:output')
body('''{ "bookName" : "foo" }''')
headers {
header('BOOK-NAME', 'foo')
}
}
}
// end::sample_dsl[]
GroovyDsl dsl2 =
// tag::sample_dsl_2[]
io.codearte.accurest.dsl.GroovyDsl.make {
label 'return_book_2'
input {
messageFrom('jms:input')
messageBody([
bookName: 'foo'
])
messageHeaders {
header('sample', 'header')
}
}
outputMessage {
sentTo('jms:output')
body([
bookName: 'foo'
])
headers {
header('BOOK-NAME', 'foo')
}
}
}
// end::sample_dsl_2[]
}