Added more docs for messaging
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -1,4 +1,4 @@
|
||||
== Accurest REST
|
||||
== Accurest HTTP
|
||||
|
||||
=== Gradle Project
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
53
docs/src/docs/asciidoc/stubrunner_msg.adoc
Normal file
53
docs/src/docs/asciidoc/stubrunner_msg.adoc
Normal 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[]
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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]
|
||||
----
|
||||
@@ -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[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user