From 52edb3c042238b2673fd8e857f88c5216b6d7c81 Mon Sep 17 00:00:00 2001 From: Tomas Bjerre Date: Fri, 30 May 2025 13:19:18 +0200 Subject: [PATCH] feat!: simplify mappings lookup (refs #95) --- .../wiremock/spring/ConfigureWireMock.java | 31 ++++-- .../internal/WireMockServerCreator.java | 98 +++++++------------ .../java/usecases/DefaultDirectoryTest.java | 35 +++++++ ...nderClasspathOverDefaultDirectoryTest.java | 51 ++++++++++ ...nderDirectoryOverDefaultDirectoryTest.java | 52 ++++++++++ .../usecases/ResetWireMockBetweenTest.java | 6 +- .../ResetWireMockDisabledBetweenTest.java | 6 +- .../usecases/SingleNamedWireMockTest.java | 2 +- .../wiremock/mappings/get-stuff.json | 16 +++ 9 files changed, 223 insertions(+), 74 deletions(-) create mode 100644 src/test/java/usecases/DefaultDirectoryTest.java create mode 100644 src/test/java/usecases/FilesUnderClasspathOverDefaultDirectoryTest.java create mode 100644 src/test/java/usecases/FilesUnderDirectoryOverDefaultDirectoryTest.java create mode 100644 src/test/resources/wiremock/mappings/get-stuff.json diff --git a/src/main/java/org/wiremock/spring/ConfigureWireMock.java b/src/main/java/org/wiremock/spring/ConfigureWireMock.java index f5e7228..a1f4647 100644 --- a/src/main/java/org/wiremock/spring/ConfigureWireMock.java +++ b/src/main/java/org/wiremock/spring/ConfigureWireMock.java @@ -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; /** @@ -15,6 +16,17 @@ */ @Retention(RetentionPolicy.RUNTIME) public @interface ConfigureWireMock { + public static final List 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. @@ -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. + * + *

It will search for mocks in this order: + *

  • In filesystem {@link #filesUnderDirectory()} + *
  • In classpath {@link #filesUnderClasspath()} + *
  • In filesystem {@link #DEFAULT_FILES_UNDER_DIRECTORY} */ - String[] filesUnderDirectory() default {"wiremock", "stubs", "mappings"}; + String[] filesUnderDirectory() default {}; /** * WireMock extensions to register in {@link WireMockServer}. diff --git a/src/main/java/org/wiremock/spring/internal/WireMockServerCreator.java b/src/main/java/org/wiremock/spring/internal/WireMockServerCreator.java index 3d61d05..7c4e075 100644 --- a/src/main/java/org/wiremock/spring/internal/WireMockServerCreator.java +++ b/src/main/java/org/wiremock/spring/internal/WireMockServerCreator.java @@ -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()); @@ -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 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 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()) { @@ -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 configureFilesUnderClasspath( - final String[] filesUnderClasspath, final String suffix) { - final List 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 configureFilesUnderDirectory( - final String[] filesUnderDirectory, final String suffix) { + private Optional findFirstExistingDirectory(final String... filesUnderDirectory) { final List 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 firstMatch = alternatives.stream().findFirst(); + if (firstMatch.isPresent()) { + this.logger.info("Using mocks from " + firstMatch.get()); + } + return firstMatch; } @SuppressFBWarnings diff --git a/src/test/java/usecases/DefaultDirectoryTest.java b/src/test/java/usecases/DefaultDirectoryTest.java new file mode 100644 index 0000000..8eff5d6 --- /dev/null +++ b/src/test/java/usecases/DefaultDirectoryTest.java @@ -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 + } + """); + } +} diff --git a/src/test/java/usecases/FilesUnderClasspathOverDefaultDirectoryTest.java b/src/test/java/usecases/FilesUnderClasspathOverDefaultDirectoryTest.java new file mode 100644 index 0000000..7a3019b --- /dev/null +++ b/src/test/java/usecases/FilesUnderClasspathOverDefaultDirectoryTest.java @@ -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 + } +] + """); + } +} diff --git a/src/test/java/usecases/FilesUnderDirectoryOverDefaultDirectoryTest.java b/src/test/java/usecases/FilesUnderDirectoryOverDefaultDirectoryTest.java new file mode 100644 index 0000000..eb7f843 --- /dev/null +++ b/src/test/java/usecases/FilesUnderDirectoryOverDefaultDirectoryTest.java @@ -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 + } +] +"""); + } +} diff --git a/src/test/java/usecases/ResetWireMockBetweenTest.java b/src/test/java/usecases/ResetWireMockBetweenTest.java index 8dd10ee..1927c21 100644 --- a/src/test/java/usecases/ResetWireMockBetweenTest.java +++ b/src/test/java/usecases/ResetWireMockBetweenTest.java @@ -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 { diff --git a/src/test/java/usecases/ResetWireMockDisabledBetweenTest.java b/src/test/java/usecases/ResetWireMockDisabledBetweenTest.java index 68005c3..30d291a 100644 --- a/src/test/java/usecases/ResetWireMockDisabledBetweenTest.java +++ b/src/test/java/usecases/ResetWireMockDisabledBetweenTest.java @@ -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 { diff --git a/src/test/java/usecases/SingleNamedWireMockTest.java b/src/test/java/usecases/SingleNamedWireMockTest.java index d063c29..ff4441c 100644 --- a/src/test/java/usecases/SingleNamedWireMockTest.java +++ b/src/test/java/usecases/SingleNamedWireMockTest.java @@ -16,7 +16,7 @@ @EnableWireMock({ @ConfigureWireMock( name = "user-client", - filesUnderClasspath = {"wiremock/user-client"}, + filesUnderClasspath = "wiremock/user-client", baseUrlProperties = "user-client.url") }) class SingleNamedWireMockTest { diff --git a/src/test/resources/wiremock/mappings/get-stuff.json b/src/test/resources/wiremock/mappings/get-stuff.json new file mode 100644 index 0000000..1c9a26a --- /dev/null +++ b/src/test/resources/wiremock/mappings/get-stuff.json @@ -0,0 +1,16 @@ +{ + "request": { + "method": "GET", + "url": "/1" + }, + "response": { + "headers": { + "Content-Type": "application/json" + }, + "jsonBody": { + "name": "Stuff", + "id": 1 + }, + "status": 200 + } +}