Modules giving you the possibility to use WireMock with different servers by using the "ambient" server embedded in a Spring Boot application. Check out the samples for more details.
Important | |
---|---|
The Spring Cloud Release Train BOM imports |
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:
@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.
If you use @AutoConfigureWireMock
then it will register WireMock
JSON stubs from the file system or classpath, by default from
file:src/test/resources/mappings
. You can customize the locations
using the stubs
attribute in the annotation, which can be a resource
pattern (ant-style) or a directory, in which case */.json
is
appended. Example:
@RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureWireMock(stubs="classpath:/stubs") public class WiremockImportApplicationTests { @Autowired private Service service; @Test public void contextLoads() throws Exception { assertThat(this.service.go()).isEqualTo("Hello World!"); } }
Note | |
---|---|
Actually WireMock always loads mappings from
|
WireMock can read response bodies from files on the classpath or file
system. In that case you will see in the JSON DSL that the response
has a "bodyFileName" instead of a (literal) "body". The files are
resolved relative to a root directory src/test/resources/__files
by
default. To customize this location you can set the files
attribute
in the @AutoConfigureWireMock
annotation to the location of the
parent directory (i.e. the place __files
is a
subdirectory). You can use Spring resource notation to refer to
file:…
or classpath:…
locations (but generic URLs are not
supported). A list of values can be given and WireMock will resolve
the first file that exists when it needs to find a response body.
Note | |
---|---|
when you configure the |
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:
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) public class WiremockForDocsClassRuleTests { // Start WireMock on some dynamic port // for some reason `dynamicPort()` is not working properly @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 allows you to stub a "secure" server with an "https" URL protocol. If your application wants to contact that stub server in an integration test, then it will find that the SSL certificates are not valid (it’s the usual problem with self-installed certificates). The best option is often to just re-configure the client to use "http", but if that’s not open to you then you can ask Spring to configure an HTTP client that ignores SSL validation errors (just for tests).
To make this work with minimum fuss you need to be using the Spring Boot RestTemplateBuilder
in your app,
e.g.
@Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder.build(); }
This is because the builder is passed through callbacks to initalize it, so the SSL validation can be set up
in the client at that point. This will happen automatically in your test if you are using the
@AutoConfigureWireMock
annotation (or the stub runner). If you are using the JUnit @Rule
approach you need
to add the @AutoConfigureHttpClient
annotation as well:
@RunWith(SpringRunner.class) @SpringBootTest("app.baseUrl=https://localhost:6443") @AutoConfigureHttpClient public class WiremockHttpsServerApplicationTests { @ClassRule public static WireMockClassRule wiremock = new WireMockClassRule( WireMockSpring.options().httpsPort(6443)); ... }
If you are using spring-boot-starter-test
then you will have the Apache HTTP client on the classpath and it will
be selected by the RestTemplateBuilder
and configured to ignore SSL errors. If you are using the default java.net
client you don’t need the annotation (but it won’t do any harm). There is no support currently for other clients, but
it may be added in future releases.
Spring Cloud Contract provides a convenience class that can load JSON WireMock stubs into a
Spring MockRestServiceServer
. Here’s an example:
@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 classpath MockRestServiceServer server = WireMockRestServiceServer.with(this.restTemplate) .baseUrl("https://example.org").stubs("classpath:/stubs/resource.json") .build(); // 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 stubs()
method takes a stub path resource pattern as an argument. 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
https://example.org/
it will get the responses as declared
there. More than one stub pattern can be specified, and each one can
be a directory (for a recursive list of all ".json"), or a fixed
filename (like in the example above) or an ant-style pattern. 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 RestDocs can be
used to generate documentation (e.g. in asciidoctor format) for an
HTTP API with Spring MockMvc or Rest Assured. At the same time as you
generate documentation for your API, you can also generate WireMock
stubs, by using Spring Cloud Contract WireMock. Just write your normal
RestDocs test cases and use @AutoConfigureRestDocs
to have stubs
automatically in the restdocs output directory. For example:
@RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureRestDocs(outputDir = "target/snippets") @AutoConfigureMockMvc public class ApplicationTests { @Autowired private MockMvc mockMvc; @Test public void contextLoads() throws Exception { mockMvc.perform(get("/resource")) .andExpect(content().string("Hello World")) .andDo(document("resource")); } }
From this test will be generated a WireMock stub at "target/snippets/stubs/resource.json". It matches all GET requests to the "/resource" path.
Without any additional configuration this will create a stub with a request matcher for the HTTP method and all headers except "host" and "content-length". To match the request more precisely, for example to match the body of a POST or PUT, we need to explicitly create a request matcher. This will do two things: 1) create a stub that only matches the way you specify, 2) assert that the request in the test case also matches the same conditions.
The main entry point for this is WireMockRestDocs.verify()
which can
be used as a substitute for the document()
convenience method. For
example:
@RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureRestDocs(outputDir = "target/snippets") @AutoConfigureMockMvc public class ApplicationTests { @Autowired private MockMvc mockMvc; @Test public void contextLoads() throws Exception { mockMvc.perform(post("/resource") .content("{\"id\":\"123456\",\"message\":\"Hello World\"}")) .andExpect(status().isOk()) .andDo(verify().jsonPath("$.id") .stub("resource")); } }
So this contract is saying: any valid POST with an "id" field will get
back an the same response as in this test. You can chain together
calls to .jsonPath()
to add additional matchers. The
JayWay documentation can help you
to get up to speed with JSON Path if it is unfamiliar to you.
Instead of the jsonPath
and contentType
convenience methods, you
can also use the WireMock APIs to verify the request matches the
created stub. Example:
@Test public void contextLoads() throws Exception { mockMvc.perform(post("/resource") .content("{\"id\":\"123456\",\"message\":\"Hello World\"}")) .andExpect(status().isOk()) .andDo(verify() .wiremock(WireMock.post( urlPathEquals("/resource")) .withRequestBody(matchingJsonPath("$.id")) .stub("post-resource")); }
The WireMock API is rich - you can match headers, query parameters, and request body by regex as well as by json path - so this can useful to create stubs with a wider range of parameters. The above example will generate a stub something like this:
post-resource.json.
{ "request" : { "url" : "/resource", "method" : "POST", "bodyPatterns" : [ { "matchesJsonPath" : "$.id" }] }, "response" : { "status" : 200, "body" : "Hello World", "headers" : { "X-Application-Context" : "application:-1", "Content-Type" : "text/plain" } } }
Note | |
---|---|
You can use either the |
On the consumer side, you can make the resource.json
generated above
available on the classpath (by publishing stubs as JARs for example).
After that, you can create a stub using WireMock in a
number of different ways, including as described above using
@AutoConfigureWireMock(stubs="classpath:resource.json")
.
Another thing that can be generated with Spring RestDocs is the Spring Cloud Contract DSL file and documentation. If you combine that with Spring Cloud WireMock then you’re getting both the contracts and stubs.
Why would you want to use this feature? Some people in the community asked questions about situation in which they would like to move to DSL based contract definition but they already have a lot of Spring MVC tests. Using this feature allows you to generate the contract files that you can later modify and move to proper folders so that the plugin picks them up.
Tip | |
---|---|
You might wonder why this functionality is in the WireMock module. Come to think of it, it does make sense since it makes little sense to generate only contracts and not generate the stubs. That’s why we suggest to do both. |
Let’s imagine the following test:
this.mockMvc.perform(post("/foo") .accept(MediaType.APPLICATION_PDF) .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .content("{\"foo\": 23, \"bar\" : \"baz\" }")) .andExpect(status().isOk()) .andExpect(content().string("bar")) // first WireMock .andDo(WireMockRestDocs.verify() .jsonPath("$[?(@.foo >= 20)]") .jsonPath("$[?(@.bar in ['baz','bazz','bazzz'])]") .contentType(MediaType.valueOf("application/json")) .stub("shouldGrantABeerIfOldEnough")) // then Contract DSL documentation .andDo(document("index", SpringCloudContractRestDocs.dslContract()));
This will lead in the creation of the stub as presented in the previous section, contract will get generated and a documentation file too.
The contract will be called index.groovy
and look more like this.
import org.springframework.cloud.contract.spec.Contract Contract.make { request { method 'POST' url '/foo' body(''' {"foo": 23 } ''') headers { header('''Accept''', '''application/json''') header('''Content-Type''', '''application/json''') } } response { status 200 body(''' bar ''') headers { header('''Content-Type''', '''application/json;charset=UTF-8''') header('''Content-Length''', '''3''') } testMatchers { jsonPath('$[?(@.foo >= 20)]', byType()) } } }
the generated document (example for Asciidoc) will contain a formatted contract
(the location of this file would be index/dsl-contract.adoc
).