Add support for file root location in @AutoConfigureWireMock

This commit is contained in:
Dave Syer
2016-11-11 14:10:27 +00:00
parent 3d4cb355c2
commit e93d951d50
11 changed files with 337 additions and 21 deletions

View File

@@ -25,10 +25,12 @@ To start the stub server on a different port use `@AutoConfigureWireMock(port=99
=== Registering Stubs Automatically
If you add a `stubs` attribute to your `@AutoConfigureWireMock` then
it will register WireMock JSON stubs from the file system or
classpath. The stubs attribute can be a resource pattern (ant-style)
or a directory, in which case `**/*.json` is appended. Example:
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)
@@ -47,6 +49,30 @@ public class WiremockImportApplicationTests {
}
----
NOTE: Actually WireMock always loads mappings from
`src/test/resources/mappings` *as well as* the custom locations in the
stubs attribute. To change this behaviour you have to also specify a
files root as described next.
=== Using Files to Specify the Stub Bodies
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 where `__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 `files` root, then it affects the
automatic loading of stubs as well (they come from the root location
in a subdirectory called "mappings"). The value of `files` has no
effect on the stubs loaded explicitly from the `stubs` attribute.
=== Alternative: Using JUnit Rules
For a more conventional WireMock experience, using JUnit `@Rules` to

View File

@@ -45,6 +45,8 @@ public @interface AutoConfigureWireMock {
int httpsPort() default -1;
String stubs() default "";
String[] stubs() default {""};
String[] files() default {""};
}

View File

@@ -18,6 +18,8 @@ package org.springframework.cloud.contract.wiremock;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
@@ -25,6 +27,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.contract.wiremock.file.ResourcesFileSource;
import org.springframework.context.SmartLifecycle;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
@@ -72,14 +75,14 @@ public class WireMockConfiguration implements SmartLifecycle {
@PostConstruct
public void init() throws IOException {
if (this.options == null) {
com.github.tomakehurst.wiremock.core.WireMockConfiguration factory = WireMockSpring
.options();
com.github.tomakehurst.wiremock.core.WireMockConfiguration factory = WireMockSpring.options();
if (this.wireMock.getPort() != 8080) {
factory.port(this.wireMock.getPort());
}
if (this.wireMock.getHttpsPort() != -1) {
factory.httpsPort(this.wireMock.getHttpsPort());
}
registerFiles(factory);
this.options = factory;
}
this.server = new WireMockServer(this.options);
@@ -90,19 +93,42 @@ public class WireMockConfiguration implements SmartLifecycle {
}
private void registerStubs() throws IOException {
if (StringUtils.hasText(this.wireMock.getStubs())) {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(
this.resourceLoader);
String pattern = this.wireMock.getStubs();
if (!pattern.contains("*")) {
for (String stubs : this.wireMock.getStubs()) {
if (StringUtils.hasText(stubs)) {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(
this.resourceLoader);
String pattern = stubs;
if (!pattern.contains("*")) {
if (!pattern.endsWith("/")) {
pattern = pattern + "/";
}
pattern = pattern + "**/*.json";
}
for (Resource resource : resolver.getResources(pattern)) {
this.server.addStubMapping(StubMapping
.buildFrom(StreamUtils.copyToString(resource.getInputStream(), Charset.forName("UTF-8"))));
}
}
}
}
private void registerFiles(com.github.tomakehurst.wiremock.core.WireMockConfiguration factory) throws IOException {
for (String files : this.wireMock.getFiles()) {
if (StringUtils.hasText(files)) {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(
this.resourceLoader);
String pattern = files;
if (!pattern.endsWith("/")) {
pattern = pattern + "/";
}
pattern = pattern + "**/*.json";
}
for (Resource resource : resolver.getResources(pattern)) {
this.server.addStubMapping(StubMapping.buildFrom(StreamUtils.copyToString(
resource.getInputStream(), Charset.forName("UTF-8"))));
List<Resource> resources = new ArrayList<>();
for (Resource resource : resolver.getResources(pattern)) {
if (resource.exists()) {
resources.add(resource);
}
}
ResourcesFileSource fileSource = new ResourcesFileSource(resources.toArray(new Resource[0]));
factory.fileSource(fileSource);
}
}
}
@@ -151,7 +177,9 @@ class WireMockProperties {
private int httpsPort = -1;
private String stubs;
private String[] stubs;
private String[] files;
public int getPort() {
return this.port;
@@ -169,12 +197,20 @@ class WireMockProperties {
this.httpsPort = httpsPort;
}
public String getStubs() {
public String[] getStubs() {
return this.stubs;
}
public void setStubs(String stubs) {
public void setStubs(String[] stubs) {
this.stubs = stubs;
}
public String[] getFiles() {
return this.files;
}
public void setFiles(String[] files) {
this.files = files;
}
}

View File

@@ -232,7 +232,7 @@ public class WireMockRestServiceServer {
if (!location.endsWith("/")) {
location = location + "/";
}
for (Resource files : resolver.getResources(location)) {
for (Resource files : this.resolver.getResources(location)) {
if (files.exists()) {
try {
Resource resource = files.createRelative(file);

View File

@@ -0,0 +1,159 @@
/*
* Copyright 2015-2016 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.file;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import com.github.tomakehurst.wiremock.common.BinaryFile;
import com.github.tomakehurst.wiremock.common.ClasspathFileSource;
import com.github.tomakehurst.wiremock.common.FileSource;
import com.github.tomakehurst.wiremock.common.SingleRootFileSource;
import com.github.tomakehurst.wiremock.common.TextFile;
/**
* @author Dave Syer
*
*/
public class ResourcesFileSource implements FileSource {
private final FileSource[] sources;
public ResourcesFileSource(Resource[] resources) {
this.sources = new FileSource[resources.length];
for (int i = 0; i < resources.length; i++) {
Resource resource = resources[i];
if (resource instanceof ClassPathResource) {
ClassPathResource classes = (ClassPathResource) resource;
this.sources[i] = new ClasspathFileSource(classes.getPath());
}
else if (resource instanceof FileSystemResource) {
FileSystemResource files = (FileSystemResource) resource;
this.sources[i] = new SingleRootFileSource(files.getFile());
}
else {
throw new IllegalArgumentException("Unsupported resource type for file source: " + resource.getClass());
}
}
}
@Override
public BinaryFile getBinaryFileNamed(String name) {
for (FileSource resource : this.sources) {
try {
UrlResource uri = new UrlResource(resource.getBinaryFileNamed(name).getUri());
if (uri.exists()) {
return resource.getBinaryFileNamed(name);
}
}
catch (IOException e) {
// Ignore
}
}
throw new IllegalStateException("Cannot create file for " + name);
}
@Override
public void createIfNecessary() {
throw new UnsupportedOperationException("Resource file sources are read-only");
}
@Override
public FileSource child(String subDirectoryName) {
for (FileSource resource : this.sources) {
try {
UrlResource uri = new UrlResource(resource.child(subDirectoryName).getUri());
if (uri.createRelative(subDirectoryName).exists()) {
return resource.child(subDirectoryName);
}
}
catch (IOException e) {
// Ignore
}
}
return this.sources[0].child(subDirectoryName);
}
@Override
public String getPath() {
for (FileSource resource : this.sources) {
try {
UrlResource uri = new UrlResource(resource.getUri());
if (uri.exists()) {
return resource.getPath();
}
}
catch (IOException e) {
// Ignore
}
}
return this.sources[0].getPath();
}
@Override
public URI getUri() {
for (FileSource resource : this.sources) {
try {
UrlResource uri = new UrlResource(resource.getUri());
if (uri.exists()) {
return resource.getUri();
}
}
catch (IOException e) {
// Ignore
}
}
return this.sources[0].getUri();
}
@Override
public List<TextFile> listFilesRecursively() {
List<TextFile> files = new ArrayList<>();
for (FileSource resource : this.sources) {
files.addAll(resource.listFilesRecursively());
}
return files;
}
@Override
public void writeTextFile(String name, String contents) {
throw new UnsupportedOperationException("Resource file sources are read-only");
}
@Override
public void writeBinaryFile(String name, byte[] contents) {
throw new UnsupportedOperationException("Resource file sources are read-only");
}
@Override
public boolean exists() {
for (FileSource resource : this.sources) {
if (resource.exists()) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,27 @@
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;
@RunWith(SpringRunner.class)
@SpringBootTest(classes=WiremockTestsApplication.class, properties="app.baseUrl=http://localhost:${wiremock.server.port}", webEnvironment=WebEnvironment.NONE)
@DirtiesContext
@AutoConfigureWireMock(port=0, files="classpath:/root/")
public class AutoConfigureWireMockFilesApplicationTests {
@Autowired
private Service service;
@Test
public void contextLoads() throws Exception {
assertThat(this.service.go()).isEqualTo("{\"message\":\"Hello Root\"}");
}
}

View File

@@ -0,0 +1,27 @@
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;
@RunWith(SpringRunner.class)
@SpringBootTest(classes=WiremockTestsApplication.class, properties="app.baseUrl=http://localhost:${wiremock.server.port}", webEnvironment=WebEnvironment.NONE)
@DirtiesContext
@AutoConfigureWireMock(port=0, files="classpath:/root/", stubs="file:src/test/resources/io.stubs/mappings")
public class AutoConfigureWireMockStubsAndFilesApplicationTests {
@Autowired
private Service service;
@Test
public void contextLoads() throws Exception {
assertThat(this.service.go()).isEqualTo("{\"message\":\"Hello Root\"}");
}
}

View File

@@ -0,0 +1,27 @@
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;
@RunWith(SpringRunner.class)
@SpringBootTest(classes=WiremockTestsApplication.class, properties="app.baseUrl=http://localhost:${wiremock.server.port}", webEnvironment=WebEnvironment.NONE)
@DirtiesContext
@AutoConfigureWireMock(port=0, files={"classpath:/nonexistent/", "classpath:/root/"}, stubs="file:src/test/resources/io.stubs/mappings")
public class AutoConfigureWireMockStubsAndMultipleFilesApplicationTests {
@Autowired
private Service service;
@Test
public void contextLoads() throws Exception {
assertThat(this.service.go()).isEqualTo("{\"message\":\"Hello Root\"}");
}
}

View File

@@ -0,0 +1 @@
{"message":"Hello Root"}

View File

@@ -0,0 +1 @@
{"message":"Hello Root"}

View File

@@ -0,0 +1,10 @@
{
"request" : {
"urlPath" : "/test",
"method" : "GET"
},
"response" : {
"status" : 200,
"bodyFileName" : "hello.json"
}
}