Skip to content

feat!: simplify mappings lookup (refs #95) #101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 22 additions & 9 deletions src/main/java/org/wiremock/spring/ConfigureWireMock.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.github.tomakehurst.wiremock.extension.ExtensionFactory;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;

/**
Expand All @@ -15,6 +16,17 @@
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface ConfigureWireMock {
public static final List<String> DEFAULT_FILES_UNDER_DIRECTORY =
List.of(
"wiremock",
"stubs",
"mappings",
"src/test/resources/wiremock",
"src/test/resources/stubs",
"src/test/resources/mappings",
"src/integtest/resources/wiremock",
"src/integtest/resources/stubs",
"src/integtest/resources/mappings");

/**
* Port on which WireMock server is going to listen.
Expand Down Expand Up @@ -80,20 +92,21 @@
String[] httpsBaseUrlProperties() default {"wiremock.server.httpsBaseUrl"};

/**
* Classpaths to pass to {@link WireMockConfiguration#usingFilesUnderClasspath(String)}. First one
* that is found will be used. If a {@link #name()} is supplied, it will first look for {@link
* #filesUnderClasspath()}/{@link #name()} enabling different mappings for differently named
* WireMocks.
* Classpaths to pass to {@link WireMockConfiguration#usingFilesUnderClasspath(String)}. See also
* {@link #filesUnderDirectory()}.
*/
String[] filesUnderClasspath() default {};
String filesUnderClasspath() default "";

/**
* Directory paths to pass to {@link WireMockConfiguration#usingFilesUnderDirectory(String)}.
* First one that is found will be used. If a {@link #name()} is supplied, it will first look for
* {@link #filesUnderClasspath()}/{@link #name()} enabling different mappings for differently
* named WireMocks.
* First existing directory will be used if list is given.
*
* <p>It will search for mocks in this order:
* <li>In filesystem {@link #filesUnderDirectory()}
* <li>In classpath {@link #filesUnderClasspath()}
* <li>In filesystem {@link #DEFAULT_FILES_UNDER_DIRECTORY}
*/
String[] filesUnderDirectory() default {"wiremock", "stubs", "mappings"};
String[] filesUnderDirectory() default {};

/**
* WireMock extensions to register in {@link WireMockServer}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,32 +48,7 @@ public WireMockServer createWireMockServer(
serverOptions.port(serverHttpPort);
}
serverOptions.notifier(new Slf4jNotifier(options.name()));

this.configureFilesUnderDirectory(options.filesUnderDirectory(), "/" + options.name())
.ifPresentOrElse(
present -> this.usingFilesUnderDirectory(serverOptions, present),
() -> {
this.configureFilesUnderDirectory(options.filesUnderDirectory(), "")
.ifPresentOrElse(
present -> this.usingFilesUnderDirectory(serverOptions, present),
() -> {
this.logger.info("No mocks found under directory");
this.configureFilesUnderClasspath(
options.filesUnderClasspath(), "/" + options.name())
.ifPresentOrElse(
present -> this.usingFilesUnderClasspath(serverOptions, present),
() -> {
this.configureFilesUnderClasspath(
options.filesUnderClasspath(), "")
.ifPresentOrElse(
present ->
this.usingFilesUnderClasspath(serverOptions, present),
() -> {
this.logger.info("No mocks found under classpath");
});
});
});
});
configureMappings(options, serverOptions);

if (options.extensionFactories().length > 0) {
serverOptions.extensionFactories(options.extensionFactories());
Expand Down Expand Up @@ -173,6 +148,30 @@ public WireMockServer createWireMockServer(
return newServer;
}

private void configureMappings(ConfigureWireMock options, WireMockConfiguration serverOptions) {
boolean isFilesUnderDirectorySupplied = options.filesUnderDirectory().length != 0;
boolean isFilesUnderClasspathSupplied = !options.filesUnderClasspath().isEmpty();
if (isFilesUnderDirectorySupplied) {
Optional<String> foundFilesUnderDirectoryOpt =
this.findFirstExistingDirectory(options.filesUnderDirectory());
if (foundFilesUnderDirectoryOpt.isEmpty()) {
throw new IllegalStateException(
"Cannot find configured mappings directory " + options.filesUnderDirectory());
}
this.usingFilesUnderDirectory(serverOptions, foundFilesUnderDirectoryOpt.get());
} else if (isFilesUnderClasspathSupplied) {
this.usingFilesUnderClasspath(serverOptions, options.filesUnderClasspath());
} else {
Optional<String> fondFilesUnderDirOpt =
this.findFirstExistingDirectory(
ConfigureWireMock.DEFAULT_FILES_UNDER_DIRECTORY.toArray(new String[0]));
fondFilesUnderDirOpt.ifPresent(s -> this.usingFilesUnderDirectory(serverOptions, s));
if (fondFilesUnderDirOpt.isEmpty()) {
this.logger.info("No mocks found under directory");
}
}
}

private int getServerHttpPortProperty(
final ConfigurableEnvironment environment, final ConfigureWireMock options) {
if (!options.usePortFromPredefinedPropertyIfFound()) {
Expand Down Expand Up @@ -217,61 +216,40 @@ private int getServerHttpsPortProperty(
.orElse(options.httpsPort());
}

private WireMockConfiguration usingFilesUnderClasspath(
private void usingFilesUnderClasspath(
final WireMockConfiguration serverOptions, final String resource) {
this.logger.info("Serving WireMock mappings from classpath resource: " + resource);
return serverOptions.usingFilesUnderClasspath(resource);
serverOptions.usingFilesUnderClasspath(resource);
}

private WireMockConfiguration usingFilesUnderDirectory(
private void usingFilesUnderDirectory(
final WireMockConfiguration serverOptions, final String dir) {
this.logger.info("Serving WireMock mappings from directory: " + dir);
return serverOptions.usingFilesUnderDirectory(dir);
}

private Optional<String> configureFilesUnderClasspath(
final String[] filesUnderClasspath, final String suffix) {
final List<String> alternatives =
List.of(filesUnderClasspath).stream()
.map(it -> it + suffix)
.filter(
it -> {
final String name = "/" + it;
final boolean exists = WireMockContextCustomizer.class.getResource(name) != null;
this.logger.info(
"Looking for mocks in classpath " + name + "... " + (exists ? "found" : ""));
return exists;
})
.toList();
if (alternatives.size() > 1) {
throw new IllegalStateException(
"Found several filesUnderClasspath: "
+ alternatives.stream().collect(Collectors.joining(", ")));
}
return alternatives.stream().findFirst();
serverOptions.usingFilesUnderDirectory(dir);
}

private Optional<String> configureFilesUnderDirectory(
final String[] filesUnderDirectory, final String suffix) {
private Optional<String> findFirstExistingDirectory(final String... filesUnderDirectory) {
final List<String> alternatives =
Stream.of(filesUnderDirectory)
.map(it -> it + suffix)
.filter(
it -> {
final File name = Path.of(it).toFile();
final boolean exists = name.exists();
final boolean exists =
Path.of(it, "mappings").toFile().exists()
|| Path.of(it, "__files").toFile().exists();
this.logger.info(
"Looking for mocks in directory " + name + "... " + (exists ? "found" : ""));
return exists;
})
.toList();
final String alternativesString = alternatives.stream().collect(Collectors.joining(", "));
if (alternatives.size() > 1) {
throw new IllegalStateException("Found several filesUnderDirectory: " + alternativesString);
}
this.logger.debug(
"Found " + alternativesString + " in " + Path.of("").toFile().getAbsolutePath());
return alternatives.stream().findFirst();
Optional<String> firstMatch = alternatives.stream().findFirst();
if (firstMatch.isPresent()) {
this.logger.info("Using mocks from " + firstMatch.get());
}
return firstMatch;
}

@SuppressFBWarnings
Expand Down
35 changes: 35 additions & 0 deletions src/test/java/usecases/DefaultDirectoryTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package usecases;

import static org.assertj.core.api.Assertions.assertThat;

import com.github.tomakehurst.wiremock.WireMockServer;
import io.restassured.RestAssured;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.wiremock.spring.ConfigureWireMock;
import org.wiremock.spring.EnableWireMock;
import org.wiremock.spring.InjectWireMock;

@SpringBootTest
@EnableWireMock({@ConfigureWireMock(name = DefaultDirectoryTest.DEFAULT_DIRECTORY_WM_NAME)})
class DefaultDirectoryTest {
public static final String DEFAULT_DIRECTORY_WM_NAME = "test-mappings";

@InjectWireMock(DefaultDirectoryTest.DEFAULT_DIRECTORY_WM_NAME)
private WireMockServer wiremock;

@Test
void usesStubFiles() {
RestAssured.baseURI = "http://localhost:" + this.wiremock.port();
final String actual =
RestAssured.when().get("/1").then().statusCode(200).extract().asPrettyString();
assertThat(actual)
.isEqualToIgnoringWhitespace(
"""
{
"name": "Stuff",
"id": 1
}
""");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package usecases;

import static org.assertj.core.api.Assertions.assertThat;

import com.github.tomakehurst.wiremock.WireMockServer;
import io.restassured.RestAssured;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.wiremock.spring.ConfigureWireMock;
import org.wiremock.spring.EnableWireMock;
import org.wiremock.spring.InjectWireMock;

@SpringBootTest
@EnableWireMock({
@ConfigureWireMock(
name = DefaultDirectoryTest.DEFAULT_DIRECTORY_WM_NAME,
filesUnderClasspath = "custom-location")
})
class FilesUnderClasspathOverDefaultDirectoryTest {

@InjectWireMock(DefaultDirectoryTest.DEFAULT_DIRECTORY_WM_NAME)
private WireMockServer wiremock;

@Test
void usesStubFiles() {
RestAssured.baseURI = "http://localhost:" + this.wiremock.port();
final String actual =
RestAssured.when()
.get("/classpathmappings")
.then()
.statusCode(200)
.extract()
.asPrettyString();
assertThat(actual)
.isEqualToIgnoringWhitespace(
"""
[
{
"id": 1,
"title": "custom location todo 1",
"userId": 1
},
{
"id": 2,
"title": "custom location todo 2",
"userId": 1
}
]
""");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package usecases;

import static org.assertj.core.api.Assertions.assertThat;

import com.github.tomakehurst.wiremock.WireMockServer;
import io.restassured.RestAssured;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.wiremock.spring.ConfigureWireMock;
import org.wiremock.spring.EnableWireMock;
import org.wiremock.spring.InjectWireMock;

@SpringBootTest
@EnableWireMock({
@ConfigureWireMock(
name = DefaultDirectoryTest.DEFAULT_DIRECTORY_WM_NAME,
filesUnderDirectory = "src/test/resources/custom-location")
})
class FilesUnderDirectoryOverDefaultDirectoryTest {

@InjectWireMock(DefaultDirectoryTest.DEFAULT_DIRECTORY_WM_NAME)
private WireMockServer wiremock;

@Test
void usesStubFiles() {
RestAssured.baseURI = "http://localhost:" + this.wiremock.port();
RestAssured.when().get("/1").then().statusCode(404).extract().asPrettyString();
final String actual =
RestAssured.when()
.get("/classpathmappings")
.then()
.statusCode(200)
.extract()
.asPrettyString();
assertThat(actual)
.isEqualToIgnoringWhitespace(
"""
[
{
"id": 1,
"title": "custom location todo 1",
"userId": 1
},
{
"id": 2,
"title": "custom location todo 2",
"userId": 1
}
]
""");
}
}
6 changes: 4 additions & 2 deletions src/test/java/usecases/ResetWireMockBetweenTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@
@ConfigureWireMock(
name = "wm1",
portProperties = "wm1.server.port",
baseUrlProperties = "wm1.server.url"),
baseUrlProperties = "wm1.server.url",
filesUnderClasspath = "nomocks"),
@ConfigureWireMock(
name = "wm2",
portProperties = "wm2.server.port",
baseUrlProperties = "wm2.server.url")
baseUrlProperties = "wm2.server.url",
filesUnderClasspath = "nomocks")
})
class ResetWireMockBetweenTest {

Expand Down
6 changes: 4 additions & 2 deletions src/test/java/usecases/ResetWireMockDisabledBetweenTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@
name = "wm1",
portProperties = "wm1.server.port",
baseUrlProperties = "wm1.server.url",
resetWireMockServer = false),
resetWireMockServer = false,
filesUnderClasspath = "nomocks"),
@ConfigureWireMock(
name = "wm2",
portProperties = "wm2.server.port",
baseUrlProperties = "wm2.server.url")
baseUrlProperties = "wm2.server.url",
filesUnderClasspath = "nomocks")
})
class ResetWireMockDisabledBetweenTest {

Expand Down
2 changes: 1 addition & 1 deletion src/test/java/usecases/SingleNamedWireMockTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
@EnableWireMock({
@ConfigureWireMock(
name = "user-client",
filesUnderClasspath = {"wiremock/user-client"},
filesUnderClasspath = "wiremock/user-client",
baseUrlProperties = "user-client.url")
})
class SingleNamedWireMockTest {
Expand Down
16 changes: 16 additions & 0 deletions src/test/resources/wiremock/mappings/get-stuff.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"request": {
"method": "GET",
"url": "/1"
},
"response": {
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"name": "Stuff",
"id": 1
},
"status": 200
}
}