diff --git a/build.gradle b/build.gradle index e3321fb..ff29cd2 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ allprojects { testImplementation platform('org.junit:junit-bom:5.11.2') testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.junit.platform:junit-platform-launcher' + testImplementation 'io.rest-assured:rest-assured:5.5.0' constraints { implementation('org.apache.commons:commons-compress:1.26.0') { @@ -58,6 +59,5 @@ project('wiremock-spring-boot-example', { implementation "org.springframework.boot:spring-boot-starter-webflux:3.3.4" testImplementation rootProject - testImplementation 'io.rest-assured:rest-assured:5.5.0' } }) diff --git a/src/main/java/org/wiremock/spring/EnableWireMock.java b/src/main/java/org/wiremock/spring/EnableWireMock.java index ac401ea..ac71d1f 100644 --- a/src/main/java/org/wiremock/spring/EnableWireMock.java +++ b/src/main/java/org/wiremock/spring/EnableWireMock.java @@ -13,7 +13,7 @@ */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) -@ExtendWith(org.wiremock.spring.internal.WireMockSpringExtension.class) +@ExtendWith(org.wiremock.spring.internal.WireMockSpringJunitExtension.class) public @interface EnableWireMock { /** * A list of {@link com.github.tomakehurst.wiremock.WireMockServer} configurations. For each diff --git a/src/main/java/org/wiremock/spring/internal/WireMockContextCustomizer.java b/src/main/java/org/wiremock/spring/internal/WireMockContextCustomizer.java index 523a167..78c9580 100644 --- a/src/main/java/org/wiremock/spring/internal/WireMockContextCustomizer.java +++ b/src/main/java/org/wiremock/spring/internal/WireMockContextCustomizer.java @@ -1,7 +1,6 @@ package org.wiremock.spring.internal; import com.github.tomakehurst.wiremock.WireMockServer; -import com.github.tomakehurst.wiremock.client.WireMock; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -45,11 +44,7 @@ public WireMockContextCustomizer(final ConfigureWireMock... configurations) { public void customizeContext( final ConfigurableApplicationContext context, final MergedContextConfiguration mergedConfig) { for (final ConfigureWireMock configureWiremock : this.configuration) { - final WireMockServer wireMockServer = - this.resolveOrCreateWireMockServer(context, configureWiremock); - if (this.configuration.size() == 1) { - WireMock.configureFor(wireMockServer.port()); - } + this.resolveOrCreateWireMockServer(context, configureWiremock); } } @@ -67,6 +62,11 @@ private WireMockServer resolveOrCreateWireMockServer( return wireMockServer; } + /** + * The docs in {@link ContextCustomizer} states that equals and hashcode is being used for caching + * and needs implementation. The customizeContext method will not be invoked for all tests, + * because of caching. + */ @Override public boolean equals(final Object o) { if (this == o) { diff --git a/src/main/java/org/wiremock/spring/internal/WireMockContextCustomizerFactory.java b/src/main/java/org/wiremock/spring/internal/WireMockContextCustomizerFactory.java index cc94065..95405b2 100644 --- a/src/main/java/org/wiremock/spring/internal/WireMockContextCustomizerFactory.java +++ b/src/main/java/org/wiremock/spring/internal/WireMockContextCustomizerFactory.java @@ -17,12 +17,19 @@ * @author Maciej Walkowiak */ public class WireMockContextCustomizerFactory implements ContextCustomizerFactory { - private static final ConfigureWireMock DEFAULT_CONFIGURE_WIREMOCK = + static final ConfigureWireMock DEFAULT_CONFIGURE_WIREMOCK = DefaultConfigureWireMock.class.getAnnotation(ConfigureWireMock.class); @ConfigureWireMock(name = "wiremock") private static class DefaultConfigureWireMock {} + static ConfigureWireMock[] getConfigureWireMocksOrDefault(final ConfigureWireMock... value) { + if (value == null || value.length == 0) { + return new ConfigureWireMock[] {WireMockContextCustomizerFactory.DEFAULT_CONFIGURE_WIREMOCK}; + } + return value; + } + @Override public ContextCustomizer createContextCustomizer( final Class testClass, final List configAttributes) { @@ -54,12 +61,7 @@ void add(final ConfigureWireMock... annotations) { void parse(final Class clazz) { final EnableWireMock annotation = AnnotationUtils.findAnnotation(clazz, EnableWireMock.class); if (annotation != null) { - final ConfigureWireMock[] value = annotation.value(); - if (value.length == 0) { - this.add(WireMockContextCustomizerFactory.DEFAULT_CONFIGURE_WIREMOCK); - } else { - this.add(value); - } + this.add(getConfigureWireMocksOrDefault(annotation.value())); } } diff --git a/src/main/java/org/wiremock/spring/internal/WireMockSpringExtension.java b/src/main/java/org/wiremock/spring/internal/WireMockSpringJunitExtension.java similarity index 61% rename from src/main/java/org/wiremock/spring/internal/WireMockSpringExtension.java rename to src/main/java/org/wiremock/spring/internal/WireMockSpringJunitExtension.java index f7dd490..9e6c893 100644 --- a/src/main/java/org/wiremock/spring/internal/WireMockSpringExtension.java +++ b/src/main/java/org/wiremock/spring/internal/WireMockSpringJunitExtension.java @@ -12,6 +12,11 @@ import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.platform.commons.support.AnnotationSupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.annotation.AnnotationUtils; +import org.wiremock.spring.ConfigureWireMock; +import org.wiremock.spring.EnableWireMock; import org.wiremock.spring.InjectWireMock; /** @@ -20,8 +25,9 @@ * * @author Maciej Walkowiak */ -public class WireMockSpringExtension +public class WireMockSpringJunitExtension implements BeforeEachCallback, AfterEachCallback, ParameterResolver { + private static final Logger LOGGER = LoggerFactory.getLogger(WireMockSpringJunitExtension.class); @Override public void beforeEach(final ExtensionContext extensionContext) throws Exception { @@ -30,6 +36,45 @@ public void beforeEach(final ExtensionContext extensionContext) throws Exception // inject properties into test class fields injectWireMockInstances(extensionContext, InjectWireMock.class, InjectWireMock::value); + + this.configureWireMockForDefaultInstance(extensionContext); + } + + private void configureWireMockForDefaultInstance(final ExtensionContext extensionContext) { + final List instances = extensionContext.getRequiredTestInstances().getAllInstances(); + WireMockServer wiremock = null; + String wireMockName = null; + for (final Object instance : instances) { + final EnableWireMock enableWireMockAnnotation = + AnnotationUtils.findAnnotation(instance.getClass(), EnableWireMock.class); + if (enableWireMockAnnotation == null) { + continue; + } + final ConfigureWireMock[] wireMockServers = + WireMockContextCustomizerFactory.getConfigureWireMocksOrDefault( + enableWireMockAnnotation.value()); + if (wireMockServers.length > 1) { + LOGGER.info( + "Not configuring WireMock for default instance when several ConfigureWireMock (" + + wireMockServers.length + + ")"); + } + if (wiremock != null) { + LOGGER.info("Not configuring WireMock for default instance when several candidates found"); + return; + } + wireMockName = wireMockServers[0].name(); + wiremock = Store.INSTANCE.findRequiredWireMockInstance(extensionContext, wireMockName); + } + if (wiremock != null) { + LOGGER.info( + "Configuring WireMock for default instance, '" + + wireMockName + + "' on '" + + wiremock.port() + + "'."); + WireMock.configureFor(wiremock.port()); + } } @Override diff --git a/src/test/java/app/NestedClassSingleWireMockTest.java b/src/test/java/app/NestedClassSingleWireMockTest.java new file mode 100644 index 0000000..bee6110 --- /dev/null +++ b/src/test/java/app/NestedClassSingleWireMockTest.java @@ -0,0 +1,81 @@ +package app; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.restassured.RestAssured; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.env.Environment; +import org.wiremock.spring.ConfigureWireMock; +import org.wiremock.spring.EnableWireMock; +import org.wiremock.spring.InjectWireMock; + +@SpringBootTest(classes = NestedClassSingleWireMockTest.AppConfiguration.class) +@EnableWireMock({ + @ConfigureWireMock( + name = "todo-service", + baseUrlProperties = "todo-service.url", + portProperties = "todo-service.port") +}) +public class NestedClassSingleWireMockTest { + + @SpringBootApplication + static class AppConfiguration {} + + @Autowired private Environment environment; + + @InjectWireMock("todo-service") + private WireMockServer topLevelClassTodoService; + + @Nested + @DisplayName("Test Something") + class NestedTest { + + @InjectWireMock("todo-service") + private WireMockServer nestedClassTodoService; + + @Test + void injectsWiremockServerToNestedClassField() { + this.assertWireMockServer( + this.nestedClassTodoService, "todo-service.url", "todo-service.port"); + } + + @Test + void injectsWiremockServerToTopLevelClassField() { + this.assertWireMockServer( + NestedClassSingleWireMockTest.this.topLevelClassTodoService, + "todo-service.url", + "todo-service.port"); + } + + private void assertWireMockServer( + final WireMockServer wireMockServer, final String property, final String portProperty) { + assertThat(wireMockServer).as("creates WireMock instance").isNotNull(); + assertThat(wireMockServer.baseUrl()).as("WireMock baseUrl is set").isNotNull(); + assertThat(wireMockServer.port()).as("sets random port").isNotZero(); + assertThat( + Integer.valueOf( + NestedClassSingleWireMockTest.this.environment.getProperty(portProperty))) + .as("sets Spring port property") + .isEqualTo(wireMockServer.port()); + assertThat(NestedClassSingleWireMockTest.this.environment.getProperty(property)) + .as("sets Spring property") + .isEqualTo(wireMockServer.baseUrl()); + + // Test that WireMock is configured for the correct WireMock instance + // Suffixed with port to make it differ for different test runs and servers + final String mockedPath = "/the_default_prop_mock-" + wireMockServer.port(); + WireMock.stubFor(get(mockedPath).willReturn(aResponse().withStatus(202))); + RestAssured.baseURI = wireMockServer.baseUrl(); + RestAssured.when().get(mockedPath).then().statusCode(202); + } + } +} diff --git a/wiremock-spring-boot-example/src/test/java/app/DefaultPropertiesTest.java b/wiremock-spring-boot-example/src/test/java/app/DefaultPropertiesTest.java index 25f51a6..f722de4 100644 --- a/wiremock-spring-boot-example/src/test/java/app/DefaultPropertiesTest.java +++ b/wiremock-spring-boot-example/src/test/java/app/DefaultPropertiesTest.java @@ -2,7 +2,9 @@ import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static org.assertj.core.api.Assertions.assertThat; +import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import io.restassured.RestAssured; import org.junit.jupiter.api.BeforeEach; @@ -10,6 +12,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.wiremock.spring.EnableWireMock; +import org.wiremock.spring.InjectWireMock; @SpringBootTest @EnableWireMock @@ -21,14 +24,26 @@ class DefaultPropertiesTest { @Value("${wiremock.server.port}") private String wiremockPort; + @InjectWireMock WireMockServer wireMockServer; + @BeforeEach - public void before() { - WireMock.stubFor(get("/the_default_prop_mock").willReturn(aResponse().withStatus(202))); - } + public void before() {} @Test - void test() { + void testCanInvoke() { + WireMock.stubFor(get("/the_default_prop_mock").willReturn(aResponse().withStatus(202))); + RestAssured.baseURI = this.wiremockUrl; RestAssured.when().get("/the_default_prop_mock").then().statusCode(202); } + + @Test + void testUrlNotNull() { + assertThat(this.wiremockUrl).isNotNull(); + } + + @Test + void testPortNotNull() { + assertThat(this.wiremockPort).isNotNull(); + } }