Add Wiremock features (Tomcat only for now)

User can depend on spring-cloud-contract-wiremock and
spring-cloud-starter-web (or tomcat) to get tomcat to run
the wiremock server. The idea is that wiremock runs in the
"ambient" server from Sprign Boot. So far we only support
Tomcat (should be easy to extend).

See tests in spring-cloud-contract-wiremock for details.
This commit is contained in:
Dave Syer
2016-07-12 18:08:24 +01:00
parent e28bf1f66e
commit 5fe510f45e
28 changed files with 977 additions and 122 deletions

13
pom.xml
View File

@@ -26,11 +26,13 @@
<properties>
<camel.version>2.17.0</camel.version>
<spring-cloud-stream.version>1.0.2.RELEASE</spring-cloud-stream.version>
<spring-framework.version>4.3.1.BUILD-SNAPSHOT</spring-framework.version>
</properties>
<modules>
<module>spring-cloud-contract-dependencies</module>
<module>docs</module>
<module>spring-cloud-contract-wiremock</module>
<module>spring-cloud-contract-verifier</module>
<module>spring-cloud-contract-spec</module>
<module>spring-cloud-contract-stub-runner</module>
@@ -62,6 +64,17 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring-framework.version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</dependencyManagement>

View File

@@ -19,7 +19,6 @@
<jsonassert.version>0.4.7</jsonassert.version>
<camel.version>2.17.0</camel.version>
<aether.version>1.1.0</aether.version>
<jetty.version>9.2.13.v20150730</jetty.version>
<spring-cloud-stream.version>1.0.2.RELEASE</spring-cloud-stream.version>
<spring-cloud-zookeeper.version>1.0.3.BUILD-SNAPSHOT</spring-cloud-zookeeper.version>
<spring-cloud-netflix.version>1.1.5.BUILD-SNAPSHOT</spring-cloud-netflix.version>
@@ -27,6 +26,11 @@
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud.contract</groupId>
<artifactId>spring-cloud-contract-wiremock</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud.contract</groupId>
<artifactId>spring-cloud-contract-spec</artifactId>
@@ -47,6 +51,11 @@
<artifactId>spring-cloud-contract-stub-runner</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud.contract</groupId>
<artifactId>spring-cloud-contract-stub-runner-jetty</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud.contract</groupId>
<artifactId>spring-cloud-contract-stub-runner-spring</artifactId>
@@ -81,91 +90,40 @@
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>${wiremock.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-annotations</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-continuation</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-deploy</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-http</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-io</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>apache-jstl</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>apache-jsp</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jmx</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-plus</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlets</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-xml</artifactId>
<version>${jetty.version}</version>
<exclusions>
<exclusion>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</exclusion>
<exclusion>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
</exclusion>
<exclusion>
<groupId>net.sf.jopt-simple</groupId>
<artifactId>jopt-simple</artifactId>
</exclusion>
<exclusion>
<artifactId>jetty-server</artifactId>
<groupId>org.eclipse.jetty</groupId>
</exclusion>
<exclusion>
<artifactId>jetty-servlet</artifactId>
<groupId>org.eclipse.jetty</groupId>
</exclusion>
<exclusion>
<artifactId>jetty-servlets</artifactId>
<groupId>org.eclipse.jetty</groupId>
</exclusion>
<exclusion>
<artifactId>jetty-webapp</artifactId>
<groupId>org.eclipse.jetty</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.toomuchcoding.jsonassert</groupId>

View File

@@ -19,6 +19,7 @@
<modules>
<module>spring-cloud-contract-stub-runner</module>
<module>spring-cloud-contract-stub-runner-jetty</module>
<module>spring-cloud-contract-stub-runner-junit</module>
<module>spring-cloud-contract-stub-runner-spring</module>
<module>spring-cloud-contract-stub-runner-boot</module>

View File

@@ -16,21 +16,24 @@
package org.springframework.cloud.contract.stubrunner.boot
import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc
import groovy.json.JsonSlurper
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.SpringApplicationContextLoader
import org.springframework.boot.test.context.SpringBootContextLoader
import org.springframework.cloud.contract.stubrunner.StubRunning
import org.springframework.cloud.stream.annotation.EnableBinding
import org.springframework.context.annotation.Configuration
import org.springframework.test.context.ContextConfiguration
import spock.lang.Specification
import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc
/**
* @author Marcin Grzejszczak
*/
// tag::boot_usage[]
@ContextConfiguration(classes = [StubRunnerBootSpec, StubRunnerBoot], loader = SpringApplicationContextLoader)
@ContextConfiguration(classes = [StubRunnerBootSpec, StubRunnerBoot], loader = SpringBootContextLoader)
@EnableBinding
@Configuration
class StubRunnerBootSpec extends Specification {

View File

@@ -0,0 +1,3 @@
=== Stub Runner Jetty
Convenience module that pulls together the dependencies needed to run the Stub Runner with wiremock, but without Spring Boot.

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud.contract</groupId>
<artifactId>spring-cloud-contract-stub-runner-build</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-cloud-contract-stub-runner-jetty</artifactId>
<packaging>jar</packaging>
<name>spring-cloud-contract-stub-runner-jetty</name>
<description>Spring Cloud Contract Stub Runner</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud.contract</groupId>
<artifactId>spring-cloud-contract-stub-runner</artifactId>
</dependency>
<dependency>
<artifactId>jetty-server</artifactId>
<groupId>org.eclipse.jetty</groupId>
</dependency>
<dependency>
<artifactId>jetty-servlet</artifactId>
<groupId>org.eclipse.jetty</groupId>
</dependency>
<dependency>
<artifactId>jetty-servlets</artifactId>
<groupId>org.eclipse.jetty</groupId>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<configuration>
<disabled>true</disabled>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -15,7 +15,7 @@
<dependencies>
<dependency>
<groupId>org.springframework.cloud.contract</groupId>
<artifactId>spring-cloud-contract-stub-runner</artifactId>
<artifactId>spring-cloud-contract-stub-runner-jetty</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>

View File

@@ -17,6 +17,10 @@
<groupId>org.springframework.cloud.contract</groupId>
<artifactId>spring-cloud-contract-stub-runner-boot</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud.contract</groupId>
<artifactId>spring-cloud-contract-wiremock</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
@@ -27,11 +31,6 @@
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
@@ -41,6 +40,16 @@
<artifactId>spock-spring</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@@ -21,15 +21,12 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerConfiguration;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.contract.stubrunner.StubFinder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.cloud.contract.stubrunner.StubFinder;
/**
* Wraps {@link DiscoveryClient} in a Stub Runner implementation that tries to find
* a corresponding WireMock server for a searched dependency
@@ -39,7 +36,6 @@ import org.springframework.cloud.contract.stubrunner.StubFinder;
@Configuration
@EnableConfigurationProperties
@ConditionalOnClass(DiscoveryClient.class)
@Import(StubRunnerConfiguration.class)
@ConditionalOnProperty(value = "stubrunner.cloud.enabled", matchIfMissing = true)
public class StubRunnerSpringCloudAutoConfiguration {

View File

@@ -18,23 +18,23 @@
<artifactId>spring-cloud-contract-stub-runner</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<scope>test</scope>
<groupId>org.springframework.cloud.contract</groupId>
<artifactId>spring-cloud-contract-wiremock</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@@ -20,6 +20,7 @@ import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.contract.stubrunner.AetherStubDownloader;
import org.springframework.cloud.contract.stubrunner.BatchStubRunner;
import org.springframework.cloud.contract.stubrunner.BatchStubRunnerFactory;
@@ -38,6 +39,7 @@ import org.springframework.core.io.Resource;
* Configuration that initializes a {@link BatchStubRunner} that runs {@link StubRunner} instance for each stub
*/
@Configuration
@ConditionalOnMissingBean(type="org.springframework.cloud.contract.wiremock.WiremockServerConfiguration")
public class StubRunnerConfiguration {
@Autowired(required = false) ContractVerifierMessaging contractVerifierMessaging;
@@ -55,7 +57,7 @@ public class StubRunnerConfiguration {
* @param workOffline forces offline work
* @param stubs comma separated list of stubs presented in Ivy notation
*/
@Bean(destroyMethod = "close")
@Bean
public StubRunning batchStubRunner(
@Value("${stubrunner.port.range.min:10000}") Integer minPortValue,
@Value("${stubrunner.port.range.max:15000}") Integer maxPortValue,

View File

@@ -50,7 +50,6 @@ class StubRunnerConfigurationSpec extends Specification {
}
@Configuration
@Import(StubRunnerConfiguration)
@EnableAutoConfiguration
static class Config {}
}

View File

@@ -17,6 +17,11 @@
<groupId>org.springframework.cloud.contract</groupId>
<artifactId>spring-cloud-contract-verifier-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud.contract</groupId>
<artifactId>spring-cloud-contract-wiremock</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud.contract</groupId>
<artifactId>spring-cloud-contract-verifier-messaging-core</artifactId>
@@ -25,18 +30,14 @@
<groupId>org.springframework.cloud.contract</groupId>
<artifactId>spring-cloud-contract-spec</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>args4j</groupId>
<artifactId>args4j</artifactId>
@@ -80,6 +81,11 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>

View File

@@ -16,13 +16,17 @@
package org.springframework.cloud.contract.stubrunner
import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.client.WireMock
import com.github.tomakehurst.wiremock.core.WireMockConfiguration
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import groovy.util.logging.Slf4j
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.wiremock.WireMockSpring
import org.springframework.util.ClassUtils
import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.client.WireMock
import com.github.tomakehurst.wiremock.core.WireMockConfiguration
@CompileStatic
@Slf4j
@@ -34,13 +38,20 @@ class StubServer {
final Collection<Contract> contracts
StubServer(int port, StubConfiguration stubConfiguration, Collection<WiremockMappingDescriptor> mappings,
Collection<Contract> contracts) {
Collection<Contract> contracts) {
this.stubConfiguration = stubConfiguration
this.mappings = mappings
this.wireMockServer = new WireMockServer(WireMockConfiguration.wireMockConfig().port(port))
this.wireMockServer = new WireMockServer(config().port(port))
this.contracts = contracts
}
private WireMockConfiguration config() {
if (ClassUtils.isPresent('org.springframework.cloud.contract.wiremock.WireMockSpring', null)) {
return WireMockSpring.config()
}
return new WireMockConfiguration()
}
StubServer start() {
wireMockServer.start()
log.info("Started stub server for project ${stubConfiguration.toColonSeparatedDependencyNotation()} on port ${wireMockServer.port()}")

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud.contract</groupId>
<artifactId>spring-cloud-contract-parent</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-cloud-contract-wiremock</artifactId>
<packaging>jar</packaging>
<name>spring-cloud-contract-wiremock</name>
<description>Spring Cloud Contract Wiremock</description>
<dependencies>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,250 @@
/*
* Copyright 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 javax.servlet.ServletContext;
import org.apache.catalina.connector.Connector;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.Banner.Mode;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration.BeanPostProcessorsRegistrar;
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
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.Ssl;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.web.context.ServletContextAware;
import com.github.tomakehurst.wiremock.common.HttpsSettings;
import com.github.tomakehurst.wiremock.common.Notifier;
import com.github.tomakehurst.wiremock.core.Options;
import com.github.tomakehurst.wiremock.core.WireMockApp;
import com.github.tomakehurst.wiremock.http.AdminRequestHandler;
import com.github.tomakehurst.wiremock.http.HttpServer;
import com.github.tomakehurst.wiremock.http.HttpServerFactory;
import com.github.tomakehurst.wiremock.http.RequestHandler;
import com.github.tomakehurst.wiremock.http.StubRequestHandler;
import com.github.tomakehurst.wiremock.servlet.WireMockHandlerDispatchingServlet;
/**
* @author Dave Syer
*
*/
public class SpringBootHttpServerFactory implements HttpServerFactory {
@Override
public HttpServer buildHttpServer(Options options,
AdminRequestHandler adminRequestHandler,
StubRequestHandler stubRequestHandler) {
return new SpringBootHttpServer(options, adminRequestHandler, stubRequestHandler);
}
}
class SpringBootHttpServer
implements HttpServer, ApplicationListener<ApplicationPreparedEvent> {
private volatile boolean running;
private Options options;
private AdminRequestHandler adminRequestHandler;
private StubRequestHandler stubRequestHandler;
private ConfigurableApplicationContext context;
public SpringBootHttpServer(Options options, AdminRequestHandler adminRequestHandler,
StubRequestHandler stubRequestHandler) {
this.options = options;
this.adminRequestHandler = adminRequestHandler;
this.stubRequestHandler = stubRequestHandler;
}
@Override
public void start() {
this.context = new SpringApplicationBuilder(WiremockServerConfiguration.class)
.logStartupInfo(false).bannerMode(Mode.OFF).listeners(this).run();
this.running = true;
}
@Override
public void stop() {
if (this.context != null) {
this.context.close();
}
this.running = false;
}
@Override
public boolean isRunning() {
return this.running;
}
@Override
public int port() {
return this.options.portNumber();
}
@Override
public int httpsPort() {
return this.options.httpsSettings().port();
}
@Override
public void onApplicationEvent(ApplicationPreparedEvent event) {
GenericApplicationContext context = (GenericApplicationContext) event
.getApplicationContext();
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
if (beanFactory.containsBean("wireMockOptions")) {
// Be idempotent (the event might be emitted more than once)
return;
}
beanFactory.registerSingleton("wireMockOptions", this.options);
beanFactory.registerSingleton("adminRequestHandler", this.adminRequestHandler);
beanFactory.registerSingleton("stubRequestHandler", this.stubRequestHandler);
beanFactory.addBeanPostProcessor(new ServerPropertiesPostProcessor());
}
class ServerPropertiesPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof ServerProperties) {
ServerProperties server = (ServerProperties) bean;
server.setPort(getPort());
setupHttps(server, SpringBootHttpServer.this.options.httpsSettings());
// TODO: other options
}
return bean;
}
private void setupHttps(ServerProperties server, HttpsSettings httpsSettings) {
if (httpsSettings.port() < 0 || !httpsSettings.enabled()) {
return;
}
Ssl ssl = server.getSsl();
if (ssl == null) {
ssl = new Ssl();
server.setSsl(ssl);
}
ssl.setKeyStore(httpsSettings.keyStorePath());
ssl.setKeyPassword(httpsSettings.keyStorePassword());
if (httpsSettings.hasTrustStore()) {
ssl.setTrustStore(httpsSettings.trustStorePath());
ssl.setTrustStorePassword(httpsSettings.trustStorePassword());
}
}
private int getPort() {
if (SpringBootHttpServer.this.options.httpsSettings().port() >= 0) {
return SpringBootHttpServer.this.options.httpsSettings().port();
}
return SpringBootHttpServer.this.options.portNumber();
}
}
}
@Configuration
@Import({ ServerPropertiesAutoConfiguration.class, BeanPostProcessorsRegistrar.class,
ConfigurationPropertiesAutoConfiguration.class, JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
class WiremockServerConfiguration {
@Autowired
private AdminRequestHandler adminRequestHandler;
@Autowired
private StubRequestHandler stubRequestHandler;
@Autowired
private Options options;
@Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
public ServletRegistrationBean stubServletRegistration() {
ServletRegistrationBean reg = new ServletRegistrationBean();
reg.addInitParameter(RequestHandler.HANDLER_CLASS_KEY,
StubRequestHandler.class.getName());
reg.setServlet(new WireMockHandlerDispatchingServlet());
reg.setName("stub");
reg.addUrlMappings("/");
return reg;
}
@Bean
public ServletRegistrationBean adminServletRegistration() {
ServletRegistrationBean reg = new ServletRegistrationBean();
reg.addInitParameter(RequestHandler.HANDLER_CLASS_KEY,
AdminRequestHandler.class.getName());
reg.setServlet(new WireMockHandlerDispatchingServlet());
reg.setName("admin");
reg.addUrlMappings(WireMockApp.ADMIN_CONTEXT_ROOT + "/*");
return reg;
}
@Bean
public ServletContextAware servletContextSetUp() {
return new ServletContextAware() {
@Override
public void setServletContext(ServletContext servletContext) {
servletContext.setAttribute(AdminRequestHandler.class.getName(),
WiremockServerConfiguration.this.adminRequestHandler);
servletContext.setAttribute(StubRequestHandler.class.getName(),
WiremockServerConfiguration.this.stubRequestHandler);
servletContext.setAttribute(Notifier.KEY, options.notifier());
}
};
}
@Bean
public EmbeddedServletContainerFactory servletContainer() {
// TODO support for other containers
TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
if (this.options.httpsSettings().enabled()) {
tomcat.addAdditionalTomcatConnectors(createStandardConnector());
}
return tomcat;
}
private Connector createStandardConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setPort(this.options.portNumber());
return connector;
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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 javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.SmartLifecycle;
import org.springframework.context.annotation.Configuration;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.Options;
/**
* @author Dave Syer
*
*/
@Configuration
public class WireMockConfiguration implements SmartLifecycle {
private volatile boolean running;
private WireMockServer server;
@Autowired(required = false)
private Options options;
@PostConstruct
public void init() {
if (options == null) {
this.options = com.github.tomakehurst.wiremock.core.WireMockConfiguration
.wireMockConfig()
.httpServerFactory(new SpringBootHttpServerFactory());
}
server = new WireMockServer(options);
}
@Override
public void start() {
server.start();
WireMock.configureFor("localhost", options.portNumber());
running = true;
}
@Override
public void stop() {
if (running) {
server.stop();
running = false;
}
}
@Override
public boolean isRunning() {
return running;
}
@Override
public int getPhase() {
return 0;
}
@Override
public boolean isAutoStartup() {
return true;
}
@Override
public void stop(Runnable callback) {
stop();
callback.run();
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 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 static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import java.io.IOException;
import java.nio.charset.Charset;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.util.StreamUtils;
import org.springframework.web.client.RestTemplate;
import com.github.tomakehurst.wiremock.common.Json;
import com.github.tomakehurst.wiremock.stubbing.StubMapping;
/**
* @author Dave Syer
*
*/
public class WireMockExpectations {
private PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
private String prefix = "classpath:/stubs/";
private String suffix = ".json";
private String baseUrl = "";
private MockRestServiceServer server;
private WireMockExpectations(RestTemplate restTemplate) {
this.server = MockRestServiceServer.bindTo(restTemplate).build();
}
public WireMockExpectations baseUrl(String baseUrl) {
this.baseUrl = baseUrl;
return this;
}
public MockRestServiceServer expect(String... locations) {
for (String location : locations) {
try {
for (Resource resource : this.resolver.getResources(this.prefix + location + this.suffix)) {
StubMapping mapping;
mapping = Json.read(StreamUtils.copyToString(resource.getInputStream(), Charset.defaultCharset()),
StubMapping.class);
this.server.expect(requestTo(this.baseUrl + mapping.getRequest().getUrlPath()))
.andRespond(withSuccess(mapping.getResponse().getBody(), MediaType.TEXT_PLAIN));
}
}
catch (IOException e) {
throw new IllegalStateException("Cannot load resources for: " + location, e);
}
}
return this.server;
}
public static WireMockExpectations with(RestTemplate restTemplate) {
WireMockExpectations result = new WireMockExpectations(restTemplate);
return result;
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 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 javax.net.ssl.HttpsURLConnection;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.ssl.SSLContexts;
import org.junit.Assert;
import org.springframework.util.ClassUtils;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
/**
* Convenience factory class for a {@link WireMockConfiguration} that knows how to use
* Spring Boot to create a stub server. Use, for example, in a JUnit rule:
*
* <pre>
* &#64;ClassRule
* public static WireMockClassRule wiremock = new WireMockClassRule(
* WireMockSpring.config());
* </pre>
*
* and then use {@link WireMock} as normal in your test methods.
*
* @author Dave Syer
*
*/
public abstract class WireMockSpring {
private static boolean initialized = false;
public static WireMockConfiguration config() {
if (!initialized) {
if (ClassUtils.isPresent("org.apache.http.conn.ssl.NoopHostnameVerifier",
null)) {
HttpsURLConnection
.setDefaultHostnameVerifier(NoopHostnameVerifier.INSTANCE);
try {
HttpsURLConnection
.setDefaultSSLSocketFactory(SSLContexts.custom()
.loadTrustMaterial(null,
TrustSelfSignedStrategy.INSTANCE)
.build().getSocketFactory());
}
catch (Exception e) {
Assert.fail("Cannot install custom socket factory: [" + e.getMessage()
+ "]");
}
}
initialized = true;
}
WireMockConfiguration config = new WireMockConfiguration();
config.httpServerFactory(new SpringBootHttpServerFactory());
return config;
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
/**
* @author Dave Syer
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(WireMockConfiguration.class)
public @interface WireMockTest {
}

View File

@@ -0,0 +1,38 @@
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.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.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import com.github.tomakehurst.wiremock.junit.WireMockClassRule;
@RunWith(SpringRunner.class)
@SpringBootTest(classes=WiremockTestsApplication.class, properties="app.baseUrl=https://localhost:8443")
@DirtiesContext
public class WiremockHttpsServerApplicationTests {
@ClassRule
public static WireMockClassRule wiremock = new WireMockClassRule(
WireMockSpring.config().httpsPort(8443));
@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:8080", webEnvironment=WebEnvironment.NONE)
@DirtiesContext
@WireMockTest
public class WiremockImportApplicationTests {
@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,35 @@
package org.springframework.cloud.contract.wiremock;
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;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;
@RunWith(SpringRunner.class)
@SpringBootTest(classes=WiremockTestsApplication.class, webEnvironment=WebEnvironment.NONE)
@DirtiesContext
public class WiremockMockServerApplicationTests {
@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();
}
}

View File

@@ -0,0 +1,38 @@
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.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.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import com.github.tomakehurst.wiremock.junit.WireMockClassRule;
@RunWith(SpringRunner.class)
@SpringBootTest(classes=WiremockTestsApplication.class, properties="app.baseUrl=http://localhost:8080", webEnvironment=WebEnvironment.NONE)
@DirtiesContext
public class WiremockServerApplicationTests {
@ClassRule
public static WireMockClassRule wiremock = new WireMockClassRule(WireMockSpring.config());
@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,59 @@
package org.springframework.cloud.contract.wiremock;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@Configuration
@EnableAutoConfiguration
@Import({Service.class, Controller.class})
public class WiremockTestsApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(WiremockTestsApplication.class, args);
}
}
@RestController
class Controller {
private final Service service;
public Controller(Service service) {
this.service = service;
}
@RequestMapping("/")
public String home() {
return this.service.go();
}
}
@Component
class Service {
@Value("${app.baseUrl:http://example.org}")
private String base;
private RestTemplate restTemplate;
public Service(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public String go() {
return this.restTemplate.getForEntity(this.base + "/resource", String.class).getBody();
}
}

View File

@@ -0,0 +1,10 @@
{
"request" : {
"urlPath" : "/resource",
"method" : "GET"
},
"response" : {
"status" : 200,
"body" : "Hello World"
}
}