Add support for port and random port in WireMock

User can @AutoConfigureWireMock(port=0) for instance. Still missing:
declarative HTTPS.
This commit is contained in:
Dave Syer
2016-07-21 17:16:16 +01:00
parent 472cd89768
commit b47ea3b0cd
10 changed files with 251 additions and 9 deletions

View File

@@ -8,7 +8,7 @@ Olga Maciaszek-Sharma, Mariusz Smykuła, Dave Syer_
== Spring Cloud Contract
What you always need it confidence in pushing new features into a new application or service in a distributed system.
What you always need is confidence in pushing new features into a new application or service in a distributed system.
This project provides support for Consumer Driven Contracts and service schemas in Spring applications, covering a
range of options for writing tests, publishing them as assets, asserting that a contract is kept by producers
and consumers, for HTTP and message-based interactions.

View File

@@ -1,4 +1,88 @@
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.
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.
Currently we support Jetty, Native WireMock server, Tomcat and Undertow.
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
@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!");
}
}
----
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]
----
@ClassRule
public static WireMockClassRule wiremock = new WireMockClassRule(
WireMockSpring.options());
----
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]
----
@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();
}
----
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/<name>.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.

View File

@@ -22,6 +22,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.test.autoconfigure.properties.PropertyMapping;
import org.springframework.context.annotation.Import;
/**
@@ -32,6 +33,9 @@ import org.springframework.context.annotation.Import;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(WireMockConfiguration.class)
@PropertyMapping("wiremock.server")
public @interface AutoConfigureWireMock {
int port() default 8080;
}

View File

@@ -40,6 +40,7 @@ import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext;
import org.springframework.boot.context.embedded.Ssl;
import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.jetty.JettyServerCustomizer;
@@ -125,11 +126,16 @@ class SpringBootHttpServer
@Override
public int port() {
return this.options.portNumber();
if (options.httpsSettings().enabled()) {
return options.portNumber();
}
EmbeddedWebApplicationContext embedded = (EmbeddedWebApplicationContext) context;
return embedded.getEmbeddedServletContainer().getPort();
}
@Override
public int httpsPort() {
// TODO HTTPS on random port
return this.options.httpsSettings().port();
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.wiremock;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.util.SocketUtils;
/**
* @author Dave Syer
*
*/
@Order(Ordered.LOWEST_PRECEDENCE)
public class WireMockApplicationListener implements ApplicationListener<ApplicationPreparedEvent> {
@Override
public void onApplicationEvent(ApplicationPreparedEvent event) {
registerPort(event.getApplicationContext().getEnvironment());
}
private void registerPort(ConfigurableEnvironment environment) {
if (environment.getProperty("wiremock.server.port", Integer.class, 0) == 0) {
MutablePropertySources propertySources = environment.getPropertySources();
if (!propertySources.contains("wiremock")) {
propertySources.addFirst(
new MapPropertySource("wiremock", new HashMap<String, Object>()));
}
Map<String, Object> source = ((MapPropertySource) propertySources
.get("wiremock")).getSource();
source.put("wiremock.server.port", SocketUtils.findAvailableTcpPort(10000, 15000));
}
}
}

View File

@@ -19,8 +19,12 @@ package org.springframework.cloud.contract.wiremock;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.SmartLifecycle;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
@@ -31,7 +35,7 @@ import com.github.tomakehurst.wiremock.core.Options;
*
*/
@Configuration
class WireMockConfiguration implements SmartLifecycle {
public class WireMockConfiguration implements SmartLifecycle, ImportAware {
private volatile boolean running;
@@ -39,13 +43,31 @@ class WireMockConfiguration implements SmartLifecycle {
@Autowired(required = false)
private Options options;
@Value("${wiremock.server.port:8080}")
private int port = 8080;
@Override
public void setImportMetadata(AnnotationMetadata metadata) {
int port = AnnotationAttributes
.fromMap(metadata
.getAnnotationAttributes(AutoConfigureWireMock.class.getName()))
.getNumber("port").intValue();
if (port>0) {
this.port = port;
}
}
@PostConstruct
public void init() {
if (options == null) {
this.options = com.github.tomakehurst.wiremock.core.WireMockConfiguration
com.github.tomakehurst.wiremock.core.WireMockConfiguration factory = com.github.tomakehurst.wiremock.core.WireMockConfiguration
.wireMockConfig()
.httpServerFactory(new SpringBootHttpServerFactory());
if (port != 8080) {
factory.port(port);
}
this.options = factory;
}
server = new WireMockServer(options);
}
@@ -53,7 +75,7 @@ class WireMockConfiguration implements SmartLifecycle {
@Override
public void start() {
server.start();
WireMock.configureFor("localhost", options.portNumber());
WireMock.configureFor("localhost", server.port());
running = true;
}

View File

@@ -0,0 +1,3 @@
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.cloud.contract.wiremock.WireMockApplicationListener

View File

@@ -15,7 +15,7 @@ import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(classes=WiremockTestsApplication.class, properties="app.baseUrl=http://localhost:8080", webEnvironment=WebEnvironment.NONE)
@SpringBootTest(classes=WiremockTestsApplication.class, properties="app.baseUrl=http://localhost:${wiremock.server.port}", webEnvironment=WebEnvironment.NONE)
@DirtiesContext
@AutoConfigureWireMock
public class AutoConfigureWireMockApplicationTests {

View File

@@ -0,0 +1,33 @@
package org.springframework.cloud.contract.wiremock;
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;
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.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = WiremockTestsApplication.class, properties = "app.baseUrl=http://localhost:${wiremock.server.port}", webEnvironment = WebEnvironment.NONE)
@DirtiesContext
@AutoConfigureWireMock(port = 9999)
public class AutoConfigureWireMockPortApplicationTests {
@Autowired
private Service service;
@Test
public void contextLoads() throws Exception {
stubFor(get(urlEqualTo("/resource")).willReturn(aResponse()
.withHeader("Content-Type", "text/plain").withBody("Hello World!")));
assertThat(this.service.go()).isEqualTo("Hello World!");
}
}

View File

@@ -0,0 +1,33 @@
package org.springframework.cloud.contract.wiremock;
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;
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.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(classes=WiremockTestsApplication.class, properties="app.baseUrl=http://localhost:${wiremock.server.port}", webEnvironment=WebEnvironment.NONE)
@DirtiesContext
@AutoConfigureWireMock(port=0)
public class AutoConfigureWireMockRandomPortApplicationTests {
@Autowired
private Service service;
@Test
public void contextLoads() throws Exception {
stubFor(get(urlEqualTo("/resource"))
.willReturn(aResponse().withHeader("Content-Type", "text/plain").withBody("Hello World!")));
assertThat(this.service.go()).isEqualTo("Hello World!");
}
}