From 70acffd9e6cf9206998a71048adcbb01dc01a120 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 22 Jul 2016 14:07:44 +0200 Subject: [PATCH] Updated docs (#48) * Added docs + tests for spec * Updated WireMock tests for docs * Made wiremock tests dynamic --- README.adoc | 121 +++++++++++++++++- .../main/asciidoc/spring-cloud-wiremock.adoc | 47 ++----- .../com/example/WiremockTestsApplication.java | 8 ++ .../WiremockForDocsClassRuleTests.java | 56 ++++++++ ...mockForDocsMockServerApplicationTests.java | 39 ++++++ .../com/example/WiremockForDocsTests.java | 51 ++++++++ .../test/resources/application-classrule.yml | 1 + .../src/test/resources/application-docs.yml | 1 + spring-cloud-contract-spec/README.adoc | 4 + .../spec/internal/ContractSpec.groovy | 80 ++++++++++++ 10 files changed, 368 insertions(+), 40 deletions(-) create mode 100644 samples/wiremock-jetty/src/test/java/com/example/WiremockForDocsClassRuleTests.java create mode 100644 samples/wiremock-jetty/src/test/java/com/example/WiremockForDocsMockServerApplicationTests.java create mode 100644 samples/wiremock-jetty/src/test/java/com/example/WiremockForDocsTests.java create mode 100644 samples/wiremock-jetty/src/test/resources/application-classrule.yml create mode 100644 samples/wiremock-jetty/src/test/resources/application-docs.yml create mode 100644 spring-cloud-contract-spec/README.adoc create mode 100644 spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy diff --git a/README.adoc b/README.adoc index 8e3569c9c0..fe981a06e9 100644 --- a/README.adoc +++ b/README.adoc @@ -13,10 +13,125 @@ and consumers, for HTTP and message-based interactions. === Spring Cloud Contract WireMock -Modules giving you the possibility to use http://wiremock.org[WireMock] with different servers. Check out the -https://github.com/spring-cloud/spring-cloud-contract/tree/master/samples[samples] for more information. +:core_path: ../../../.. +:doc_samples: {core_path}/samples/wiremock-jetty -Currently we support Jetty, Native WireMock server, Tomcat and Undertow. +Modules giving you the possibility to use +http://wiremock.org[WireMock] with different servers by using the +"ambient" server embedded in a Spring Boot application. Check out the +https://github.com/spring-cloud/spring-cloud-contract/tree/master/samples[samples] +for more details. + +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 +and add `@AutoConfigureWireMock` in order to be able to use Wiremock +in your tests. Wiremock runs as a stub server and you can register +stub behaviour using a Java API or via static JSON declarations as +part of your test. Here's a simple example: + +[source,java,indent=0] +---- +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@AutoConfigureWireMock(port = 0) +public class WiremockForDocsTests { + // A service that calls out over HTTP + @Autowired private Service service; + + // Using the WireMock APIs in the normal way: + @Test + public void contextLoads() throws Exception { + // Stubbing WireMock + stubFor(get(urlEqualTo("/resource")) + .willReturn(aResponse().withHeader("Content-Type", "text/plain").withBody("Hello World!"))); + // We're asserting if WireMock responded properly + assertThat(this.service.go()).isEqualTo("Hello World!"); + } + +} +---- + +To start the stub server on a different port use `@AutoConfigureWireMock(port=9999)` (for example), and for a random port use the value 0. The stub server port will be bindable in the test application context as "wiremock.server.port". Using `@AutoConfigureWireMock` adds a bean of type `WiremockConfiguration` to your test application context, where it will be cached in between methods and classes having the same context, just like for normal Spring integration tests. + +For a more conventional WireMock experience, using JUnit `@Rules` to +start and stop the server, just use the `WireMockSpring` convenience +class to obtain an `Options` instance: + +[source,java,indent=0] +---- +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@AutoConfigureWireMock +public class WiremockForDocsClassRuleTests { + + // Start WireMock on some dynamic port + @ClassRule + public static WireMockClassRule wiremock = new WireMockClassRule( + WireMockSpring.options().dynamicPort()); + // A service that calls out over HTTP to localhost:${wiremock.port} + @Autowired + private Service service; + + // Using the WireMock APIs in the normal way: + @Test + public void contextLoads() throws Exception { + // Stubbing WireMock + wiremock.stubFor(get(urlEqualTo("/resource")) + .willReturn(aResponse().withHeader("Content-Type", "text/plain").withBody("Hello World!"))); + // We're asserting if WireMock responded properly + assertThat(this.service.go()).isEqualTo("Hello World!"); + } + +} +---- + +The use `@ClassRule` means that the server will shut down after all the methods in this class. + +== WireMock and Spring MVC Mocks + +Spring Cloud Contract provides a convenience class that can load JSON WireMock stubs into a +Spring `MockRestServiceServer`. Here's an example: + +[source,java,indent=0] +---- +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment=WebEnvironment.NONE) +public class WiremockForDocsMockServerApplicationTests { + + @Autowired + private RestTemplate restTemplate; + + @Autowired + private Service service; + + @Test + public void contextLoads() throws Exception { + // will read stubs from default /resources/stubs location + MockRestServiceServer server = WireMockExpectations.with(this.restTemplate) + .baseUrl("http://example.org") + .expect("resource"); + // We're asserting if WireMock responded properly + assertThat(this.service.go()).isEqualTo("Hello World"); + server.verify(); + } +} +---- + +The `baseUrl` is prepended to all mock calls, and the `expect()` +method takes a stub name as an argument, where the stubs are stored in +the classpath at `/stubs/.json` by default. So in this example +the stub defined at `/stubs/resource.json` is loaded into the mock +server, so if the `RestTemplate` is asked to visit +`http://example.org/` it will get the responses as declared there. The +JSON format is the normal WireMock format which you can read about in +the WireMock website. + +Currently we support Tomcat, Jetty and Undertow as Spring Boot +embedded servers, and Wiremock itself has "native" support for a +particular version of Jetty (currently 9.2). To use the native Jetty +you need to add the native wiremock dependencies and exclude the +Spring Boot container if there is one. === Spring Cloud Contract Verifier diff --git a/docs/src/main/asciidoc/spring-cloud-wiremock.adoc b/docs/src/main/asciidoc/spring-cloud-wiremock.adoc index 9abc93d0fb..3fb76a03b6 100644 --- a/docs/src/main/asciidoc/spring-cloud-wiremock.adoc +++ b/docs/src/main/asciidoc/spring-cloud-wiremock.adoc @@ -1,3 +1,6 @@ +:core_path: ../../../.. +:doc_samples: {core_path}/samples/wiremock-jetty + Modules giving you the possibility to use http://wiremock.org[WireMock] with different servers by using the "ambient" server embedded in a Spring Boot application. Check out the @@ -14,24 +17,8 @@ part of your test. Here's a simple example: [source,java,indent=0] ---- -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureWireMock -public class WiremockImportApplicationTests { - - // A service that calls out over HTTP to localhost:8080 - @Autowired - private Service service; - - @Test - public void contextLoads() throws Exception { - // Using the WireMock APIs in the normal way: - stubFor(get(urlEqualTo("/resource")) - .willReturn(aResponse().withHeader("Content-Type", "text/plain").withBody("Hello World!"))); - assertThat(this.service.go()).isEqualTo("Hello World!"); - } - -} +include::{doc_samples}/src/test/java/com/example/WiremockForDocsTests.java[tags=wiremock_test1] +include::{doc_samples}/src/test/java/com/example/WiremockForDocsTests.java[tags=wiremock_test2] ---- To start the stub server on a different port use `@AutoConfigureWireMock(port=9999)` (for example), and for a random port use the value 0. The stub server port will be bindable in the test application context as "wiremock.server.port". Using `@AutoConfigureWireMock` adds a bean of type `WiremockConfiguration` to your test application context, where it will be cached in between methods and classes having the same context, just like for normal Spring integration tests. @@ -42,34 +29,20 @@ class to obtain an `Options` instance: [source,java,indent=0] ---- - @ClassRule - public static WireMockClassRule wiremock = new WireMockClassRule( - WireMockSpring.options()); +include::{doc_samples}/src/test/java/com/example/WiremockForDocsClassRuleTests.java[tags=wiremock_test1] +include::{doc_samples}/src/test/java/com/example/WiremockForDocsClassRuleTests.java[tags=wiremock_test2] ---- The use `@ClassRule` means that the server will shut down after all the methods in this class. == WireMock and Spring MVC Mocks -Spring Cloud Contract provides a convenience class that can load JSON WireMock stubs into a Spring `MockRestServiceServer`. Here's an example: +Spring Cloud Contract provides a convenience class that can load JSON WireMock stubs into a +Spring `MockRestServiceServer`. Here's an example: [source,java,indent=0] ---- - @Autowired - private RestTemplate restTemplate; - - @Autowired - private Service service; - - @Test - public void contextLoads() throws Exception { - MockRestServiceServer server = WireMockExpectations.with(this.restTemplate) - .baseUrl("http://example.org") - .expect("resource"); - assertThat(this.service.go()).isEqualTo("Hello World"); - server.verify(); - } - +include::{doc_samples}/src/test/java/com/example/WiremockForDocsMockServerApplicationTests.java[tags=wiremock_test] ---- The `baseUrl` is prepended to all mock calls, and the `expect()` diff --git a/samples/wiremock-jetty/src/main/java/com/example/WiremockTestsApplication.java b/samples/wiremock-jetty/src/main/java/com/example/WiremockTestsApplication.java index fa59698f17..9736043a64 100644 --- a/samples/wiremock-jetty/src/main/java/com/example/WiremockTestsApplication.java +++ b/samples/wiremock-jetty/src/main/java/com/example/WiremockTestsApplication.java @@ -52,4 +52,12 @@ class Service { public String go() { return this.restTemplate.getForEntity(this.base + "/resource", String.class).getBody(); } + + public String getBase() { + return base; + } + + public void setBase(String base) { + this.base = base; + } } diff --git a/samples/wiremock-jetty/src/test/java/com/example/WiremockForDocsClassRuleTests.java b/samples/wiremock-jetty/src/test/java/com/example/WiremockForDocsClassRuleTests.java new file mode 100644 index 0000000000..8bc790dffa --- /dev/null +++ b/samples/wiremock-jetty/src/test/java/com/example/WiremockForDocsClassRuleTests.java @@ -0,0 +1,56 @@ +package com.example; + +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; +import org.springframework.cloud.contract.wiremock.WireMockSpring; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +import com.github.tomakehurst.wiremock.junit.WireMockClassRule; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static org.assertj.core.api.Assertions.assertThat; + +@ActiveProfiles("classrule") +@DirtiesContext +//tag::wiremock_test1[] +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@AutoConfigureWireMock +public class WiremockForDocsClassRuleTests { + + // Start WireMock on some dynamic port + @ClassRule + public static WireMockClassRule wiremock = new WireMockClassRule( + WireMockSpring.options().dynamicPort()); +//end::wiremock_test1[] + @Before + public void setup() { + this.service.setBase("http://localhost:" + wiremock.port()); + } +//tag::wiremock_test2[] + // A service that calls out over HTTP to localhost:${wiremock.port} + @Autowired + private Service service; + + // Using the WireMock APIs in the normal way: + @Test + public void contextLoads() throws Exception { + // Stubbing WireMock + wiremock.stubFor(get(urlEqualTo("/resource")) + .willReturn(aResponse().withHeader("Content-Type", "text/plain").withBody("Hello World!"))); + // We're asserting if WireMock responded properly + assertThat(this.service.go()).isEqualTo("Hello World!"); + } + +} +//end::wiremock_test2[] \ No newline at end of file diff --git a/samples/wiremock-jetty/src/test/java/com/example/WiremockForDocsMockServerApplicationTests.java b/samples/wiremock-jetty/src/test/java/com/example/WiremockForDocsMockServerApplicationTests.java new file mode 100644 index 0000000000..ef885dc08d --- /dev/null +++ b/samples/wiremock-jetty/src/test/java/com/example/WiremockForDocsMockServerApplicationTests.java @@ -0,0 +1,39 @@ +package com.example; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.cloud.contract.wiremock.WireMockExpectations; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.Assertions.assertThat; + +@DirtiesContext +//tag::wiremock_test[] +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment=WebEnvironment.NONE) +public class WiremockForDocsMockServerApplicationTests { + + @Autowired + private RestTemplate restTemplate; + + @Autowired + private Service service; + + @Test + public void contextLoads() throws Exception { + // will read stubs from default /resources/stubs location + MockRestServiceServer server = WireMockExpectations.with(this.restTemplate) + .baseUrl("http://example.org") + .expect("resource"); + // We're asserting if WireMock responded properly + assertThat(this.service.go()).isEqualTo("Hello World"); + server.verify(); + } +} +//end::wiremock_test[] diff --git a/samples/wiremock-jetty/src/test/java/com/example/WiremockForDocsTests.java b/samples/wiremock-jetty/src/test/java/com/example/WiremockForDocsTests.java new file mode 100644 index 0000000000..80ee38dd34 --- /dev/null +++ b/samples/wiremock-jetty/src/test/java/com/example/WiremockForDocsTests.java @@ -0,0 +1,51 @@ +package com.example; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; +import org.springframework.core.env.Environment; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static org.assertj.core.api.Assertions.assertThat; + +@ActiveProfiles("docs") +@DirtiesContext +//tag::wiremock_test1[] +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@AutoConfigureWireMock(port = 0) +public class WiremockForDocsTests { +//end::wiremock_test1[] + + @Autowired Environment environment; + + @Before + public void setup() { + service.setBase("http://localhost:" + this.environment.getProperty("wiremock.server.port")); + } +//tag::wiremock_test2[] + // A service that calls out over HTTP + @Autowired private Service service; + + // Using the WireMock APIs in the normal way: + @Test + public void contextLoads() throws Exception { + // Stubbing WireMock + stubFor(get(urlEqualTo("/resource")) + .willReturn(aResponse().withHeader("Content-Type", "text/plain").withBody("Hello World!"))); + // We're asserting if WireMock responded properly + assertThat(this.service.go()).isEqualTo("Hello World!"); + } + +} +//end::wiremock_test2[] \ No newline at end of file diff --git a/samples/wiremock-jetty/src/test/resources/application-classrule.yml b/samples/wiremock-jetty/src/test/resources/application-classrule.yml new file mode 100644 index 0000000000..aac0711d07 --- /dev/null +++ b/samples/wiremock-jetty/src/test/resources/application-classrule.yml @@ -0,0 +1 @@ +app.baseUrl: http://localhost:9090 \ No newline at end of file diff --git a/samples/wiremock-jetty/src/test/resources/application-docs.yml b/samples/wiremock-jetty/src/test/resources/application-docs.yml new file mode 100644 index 0000000000..662aebfb27 --- /dev/null +++ b/samples/wiremock-jetty/src/test/resources/application-docs.yml @@ -0,0 +1 @@ +app.baseUrl: http://localhost:8080 \ No newline at end of file diff --git a/spring-cloud-contract-spec/README.adoc b/spring-cloud-contract-spec/README.adoc new file mode 100644 index 0000000000..4761e48814 --- /dev/null +++ b/spring-cloud-contract-spec/README.adoc @@ -0,0 +1,4 @@ +=== Spring Cloud Contract Specification + +This module contains the specifications describing contracts. Currently we have only +one way for contract description which is the Groovy DSL. \ No newline at end of file diff --git a/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy b/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy new file mode 100644 index 0000000000..6ba474381e --- /dev/null +++ b/spring-cloud-contract-spec/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy @@ -0,0 +1,80 @@ +package org.springframework.cloud.contract.spec.internal + +import org.springframework.cloud.contract.spec.Contract +import spock.lang.Specification +/** + * @author Marcin Grzejszczak + */ +class ContractSpec extends Specification { + + def 'should work for http'() { + when: + Contract.make { + request { + url('/foo') + method('PUT') + headers { + header([ + foo: 'bar' + ]) + } + body([ + foo: 'bar' + ]) + } + response { + headers { + header([ + foo2: 'bar' + ]) + } + body([ + foo2: 'bar' + ]) + } + } + then: + noExceptionThrown() + } + def 'should work for messaging'() { + when: + Contract.make { + input { + messageFrom('input') + messageBody([ + foo: 'bar' + ]) + messageHeaders { + header([ + foo: 'bar' + ]) + } + } + outputMessage { + sentTo('output') + body([ + foo2: 'bar' + ]) + headers { + header([ + foo2: 'bar' + ]) + } + } + } + then: + noExceptionThrown() + } + + def 'should generate a value if only regex is passed for client'() { + given: + Request request = new Request() + DslProperty property + when: + request.with { + property = value(consumer(regex("[0-9]{5}"))) + } + then: + (property.serverValue as String).matches(/[0-9]{5}/) + } +}