diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b025830f8..0b8eb2bb4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -51,6 +51,7 @@ jansi = "2.4.1" managed-opensearch-testcontainers = "2.1.3" managed-testcontainers = "1.20.6" managed-testcontainers-redis = "1.6.4" +managed-testcontainers-wiremock = "1.0-alpha-13" [libraries] # Testcontainers @@ -80,6 +81,7 @@ managed-testcontainers-rabbitmq = { module = "org.testcontainers:rabbitmq", vers managed-testcontainers-redis = { module = "com.redis.testcontainers:testcontainers-redis", version.ref = "managed-testcontainers-redis" } managed-testcontainers-r2dbc = { module = "org.testcontainers:r2dbc", version.ref = "managed-testcontainers" } managed-testcontainers-vault = { module = "org.testcontainers:vault", version.ref = "managed-testcontainers" } +managed-testcontainers-wiremock = { module = "org.wiremock.integrations.testcontainers:wiremock-testcontainers-module", version.ref = "managed-testcontainers-wiremock" } # Core micronaut-core = { module = 'io.micronaut:micronaut-core-bom', version.ref = 'micronaut' } diff --git a/settings.gradle b/settings.gradle index 0befc7d86..357e77e20 100644 --- a/settings.gradle +++ b/settings.gradle @@ -80,6 +80,7 @@ include 'test-resources-solr' include 'test-resources-testcontainers' include 'test-resources-hashicorp-vault' include 'test-resources-hashicorp-consul' +include 'test-resources-wiremock' extensionModules.each { String projectName = "test-resources-extensions-$it" diff --git a/src/main/docs/guide/modules-wiremock.adoc b/src/main/docs/guide/modules-wiremock.adoc new file mode 100644 index 000000000..08537f8f9 --- /dev/null +++ b/src/main/docs/guide/modules-wiremock.adoc @@ -0,0 +1,12 @@ +Wiremock support will automatically start a https://https://wiremock.org/[Wiremock container] and provide the value of the `wiremock.port`, `wiremock.host` and `wiremock.url` properties. + +The default image can be overwritten by setting the `test-resources.containers.wiremock.image-name` property. + +You can pass https://wiremock.org/docs/standalone/docker/#start-with-command-line-arguments[cli arguments] to the Wiremock container by setting the `test-resources.containers.wiremock.cli-args` property. + +--- +test-resources: + containers: + wiremock: + cli-args: "--verbose --global-response-templating" +--- diff --git a/test-resources-wiremock/build.gradle.kts b/test-resources-wiremock/build.gradle.kts new file mode 100644 index 000000000..92f9abb2d --- /dev/null +++ b/test-resources-wiremock/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("io.micronaut.build.internal.testcontainers-module") +} + +description = """ +Provides core support for Wiremock test resources. +""" + +dependencies { + api(libs.managed.testcontainers.wiremock) +} diff --git a/test-resources-wiremock/src/main/java/io/micronaut/testresources/wiremock/WiremockTestResourceProvider.java b/test-resources-wiremock/src/main/java/io/micronaut/testresources/wiremock/WiremockTestResourceProvider.java new file mode 100644 index 000000000..64a1fcf09 --- /dev/null +++ b/test-resources-wiremock/src/main/java/io/micronaut/testresources/wiremock/WiremockTestResourceProvider.java @@ -0,0 +1,82 @@ +/* + * Copyright 2017-2024 original 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 + * + * https://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 io.micronaut.testresources.wiremock; + +import io.micronaut.testresources.testcontainers.AbstractTestContainersProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.utility.DockerImageName; +import org.wiremock.integrations.testcontainers.WireMockContainer; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * A test resource provider which will spawn an Wiremock test container. + */ +public class WiremockTestResourceProvider extends AbstractTestContainersProvider { + + public static final String WIREMOCK_PORT = "wiremock.port"; + public static final String WIREMOCK_HOST = "wiremock.host"; + public static final String WIREMOCK_URL = "wiremock.url"; + public static final List SUPPORTED_PROPERTIES_LIST = List.of(WIREMOCK_PORT, WIREMOCK_HOST, WIREMOCK_URL); + public static final String CLI_ARGS_PROPERTY_PATH = "containers.wiremock.cli-args"; + + @Override + public String getDisplayName() { + return "Wiremock"; + } + + @Override + protected String getSimpleName() { + return "wiremock"; + } + + @Override + protected String getDefaultImageName() { + return "wiremock/wiremock:3.13.0"; + } + + @Override + protected WireMockContainer createContainer(DockerImageName imageName, Map requestedProperties, Map testResourcesConfig) { + System.out.println(testResourcesConfig); + return new WireMockContainer(imageName).withCliArg(testResourcesConfig.get(CLI_ARGS_PROPERTY_PATH) != null ? (String) testResourcesConfig.get(CLI_ARGS_PROPERTY_PATH) : ""); + } + + @Override + protected Optional resolveProperty(String propertyName, WireMockContainer container) { + if (propertyName.equals(WIREMOCK_PORT)) { + return Optional.of(container.getPort().toString()); + } else if (propertyName.equals(WIREMOCK_HOST)) { + return Optional.of(container.getHost()); + } else if (propertyName.equals(WIREMOCK_URL)) { + return Optional.of(container.getBaseUrl()); + } + return Optional.empty(); + } + + @Override + public List getResolvableProperties(Map> propertyEntries, Map testResourcesConfig) { + return SUPPORTED_PROPERTIES_LIST; + } + + @Override + protected boolean shouldAnswer(String propertyName, Map requestedProperties, Map testResourcesConfig) { + return SUPPORTED_PROPERTIES_LIST.contains(propertyName); + } +} diff --git a/test-resources-wiremock/src/main/resources/META-INF/services/io.micronaut.testresources.core.TestResourcesResolver b/test-resources-wiremock/src/main/resources/META-INF/services/io.micronaut.testresources.core.TestResourcesResolver new file mode 100644 index 000000000..cae26191e --- /dev/null +++ b/test-resources-wiremock/src/main/resources/META-INF/services/io.micronaut.testresources.core.TestResourcesResolver @@ -0,0 +1 @@ +io.micronaut.testresources.wiremock.WiremockTestResourceProvider diff --git a/test-resources-wiremock/src/test/groovy/io/micronaut/testresources/solr/AbstractWiremockSpec.groovy b/test-resources-wiremock/src/test/groovy/io/micronaut/testresources/solr/AbstractWiremockSpec.groovy new file mode 100644 index 000000000..1747b2270 --- /dev/null +++ b/test-resources-wiremock/src/test/groovy/io/micronaut/testresources/solr/AbstractWiremockSpec.groovy @@ -0,0 +1,32 @@ +package io.micronaut.testresources.solr + +import io.micronaut.context.annotation.Value +import io.micronaut.testresources.testcontainers.AbstractTestContainersSpec +import jakarta.inject.Singleton + +abstract class AbstractWiremockSpec extends AbstractTestContainersSpec { + + @Override + String getScopeName() { + 'wiremock' + } + + @Override + String getImageName() { + 'wiremock' + } + + @Singleton + static class WiremockClient { + + @Value('${wiremock.port}') + int port + + @Value('${wiremock.host}') + String host + + @Value('${wiremock.url}') + String url + } + +} diff --git a/test-resources-wiremock/src/test/groovy/io/micronaut/testresources/solr/WiremockConfigurationSpec.groovy b/test-resources-wiremock/src/test/groovy/io/micronaut/testresources/solr/WiremockConfigurationSpec.groovy new file mode 100644 index 000000000..b14775d1f --- /dev/null +++ b/test-resources-wiremock/src/test/groovy/io/micronaut/testresources/solr/WiremockConfigurationSpec.groovy @@ -0,0 +1,43 @@ +package io.micronaut.testresources.solr + +import io.micronaut.context.ApplicationContext +import io.micronaut.context.annotation.Value +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import io.micronaut.testresources.core.Scope +import io.micronaut.testresources.testcontainers.TestContainers +import jakarta.inject.Inject +import jakarta.inject.Singleton +import spock.lang.Shared + +@MicronautTest +class WiremockConfigurationSpec extends AbstractWiremockSpec { + + @Inject + ApplicationContext applicationContext + + @Shared + String testResourcePath = 'src/test/resources' + + def 'verify Wiremock container configuration'() { + when: 'we get the Wiremock client' + def client = applicationContext.getBean(WiremockClient) + + then: 'container is running' + listContainers().size() == 1 + + and: 'Port is configured' + client.port < 65536 && client.port > 0 + + and: 'Host is configured' + client.host == 'localhost' + + and: 'URL is configured' + client.url == "http://${client.host}:${client.port}" + + and: 'The container is using the correct image' + with(TestContainers.listByScope('wiremock').get(Scope.of('wiremock'))) { + size() == 1 + get(0).dockerImageName == 'wiremock/wiremock:3.13.0' + } + } +} diff --git a/test-resources-wiremock/src/test/groovy/io/micronaut/testresources/solr/WiremockWithCliArgsSpec.groovy b/test-resources-wiremock/src/test/groovy/io/micronaut/testresources/solr/WiremockWithCliArgsSpec.groovy new file mode 100644 index 000000000..7e5c225ed --- /dev/null +++ b/test-resources-wiremock/src/test/groovy/io/micronaut/testresources/solr/WiremockWithCliArgsSpec.groovy @@ -0,0 +1,35 @@ +package io.micronaut.testresources.solr + +import io.micronaut.context.ApplicationContext +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import io.micronaut.testresources.core.Scope +import io.micronaut.testresources.testcontainers.TestContainers +import jakarta.inject.Inject +import spock.lang.Shared + +@MicronautTest(environments = "cli-args") +class WiremockWithCliArgsSpec extends AbstractWiremockSpec { + + @Inject + ApplicationContext applicationContext + + @Shared + String testResourcePath = 'src/test/resources' + + def 'verify Wiremock container configured with cli args'() { + when: 'we get the Wiremock client' + def client = applicationContext.getBean(WiremockClient) + + and: 'Port is configured' + client.port == 8080 + + then: 'The cli args are passed to the container command' + with(TestContainers.listByScope('wiremock').get(Scope.of('wiremock'))) { + size() == 1 + get(0).getCommandParts().contains('--port') + get(0).getCommandParts().contains('8080') + get(0).getCommandParts().contains('--verbose') + get(0).getCommandParts().contains('--global-response-templating') + } + } +} diff --git a/test-resources-wiremock/src/test/resources/application-cli-args.yml b/test-resources-wiremock/src/test/resources/application-cli-args.yml new file mode 100644 index 000000000..0c9c700ff --- /dev/null +++ b/test-resources-wiremock/src/test/resources/application-cli-args.yml @@ -0,0 +1,4 @@ +test-resources: + containers: + wiremock: + cli-args: "--port 8080 --verbose --global-response-templating" diff --git a/test-resources-wiremock/src/test/resources/logback.xml b/test-resources-wiremock/src/test/resources/logback.xml new file mode 100644 index 000000000..44b79c40d --- /dev/null +++ b/test-resources-wiremock/src/test/resources/logback.xml @@ -0,0 +1,14 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + +