diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockApplicationListener.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockApplicationListener.java index 1ba16141dd..f964c6264a 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockApplicationListener.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockApplicationListener.java @@ -97,7 +97,11 @@ public class WireMockApplicationListener implements ApplicationListener source = ((MapPropertySource) propertySources.get("wiremock")).getSource(); - source.put(portProperty, SocketUtils.findAvailableTcpPort(minPort, maxPort)); + int port = SocketUtils.findAvailableTcpPort(minPort, maxPort); + source.put(portProperty, port); + if (log.isDebugEnabled()) { + log.debug("Registered property source for property [" + portProperty + "] with value [" + port + "]"); + } source.put(dynamicPortProperty, true); } diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockConfiguration.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockConfiguration.java index 88556e1cf9..66902a28ed 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockConfiguration.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockConfiguration.java @@ -124,7 +124,7 @@ public class WireMockConfiguration implements SmartLifecycle { private void reRegisterBeans() { if (!this.beanFactory.containsBean(WIREMOCK_SERVER_BEAN_NAME)) { if (log.isDebugEnabled()) { - log.debug("Registering WireMock [" + this.server + "] instance"); + printRegistrationLog(); } this.beanFactory.registerSingleton(WIREMOCK_SERVER_BEAN_NAME, this.server); } @@ -134,12 +134,20 @@ public class WireMockConfiguration implements SmartLifecycle { } this.beanFactory.destroySingleton(WIREMOCK_SERVER_BEAN_NAME); if (log.isDebugEnabled()) { - log.debug("Registering WireMock [" + this.server + "] instance"); + printRegistrationLog(); } this.beanFactory.registerSingleton(WIREMOCK_SERVER_BEAN_NAME, this.server); } } + private void printRegistrationLog() { + int httpPort = this.server.isRunning() ? this.server.port() : -1; + int httpsPort = this.server.isRunning() && this.server.getOptions().httpsSettings().enabled() + ? this.server.httpsPort() : -1; + log.debug("Registering WireMock [" + this.server + "] at http port [" + httpPort + "] and https port [" + + httpsPort + "]"); + } + private void reRegisterServer() { if (log.isTraceEnabled()) { log.trace("Creating a new server at http port [" + this.wireMock.getServer().getPort() + "] and " @@ -154,12 +162,20 @@ public class WireMockConfiguration implements SmartLifecycle { else if (this.server == null) { this.server = new WireMockServer(this.options); if (log.isDebugEnabled()) { - log.debug("Created new server [" + this.server + "] at port [" + port(this.server) + "]"); + int httpPort = this.server.isRunning() ? this.server.port() : -1; + int httpsPort = this.server.isRunning() && this.server.getOptions().httpsSettings().enabled() + ? this.server.httpsPort() : -1; + log.debug("Created new server [" + this.server + "] at http port [" + httpPort + "] and https port [" + + httpsPort + "]"); } } start(); if (log.isDebugEnabled()) { - log.debug("Started server [" + this.server + "] at port [" + port(this.server) + "]"); + int httpPort = this.server.isRunning() ? this.server.port() : -1; + int httpsPort = this.server.isRunning() && this.server.getOptions().httpsSettings().enabled() + ? this.server.httpsPort() : -1; + log.debug("Started server [" + this.server + "] at http port [" + httpPort + "] and https port [" + + httpsPort + "]"); } logRegisteredMappings(); } @@ -244,10 +260,14 @@ public class WireMockConfiguration implements SmartLifecycle { @Override public void start() { if (isRunning()) { - int port = port(this.server); + int httpPort = this.server.isRunning() ? this.server.port() : -1; + int httpsPort = this.server.isRunning() && this.server.getOptions().httpsSettings().enabled() + ? this.server.httpsPort() : -1; if (log.isDebugEnabled()) { - log.debug("Server [" + this.server + "] is already running at port [" + port + "]"); + log.debug("Server [" + this.server + "] is already running at http port [" + httpPort + + "] / https port [" + httpsPort + "]"); } + updateCurrentServer(); return; } this.server.start(); @@ -258,8 +278,11 @@ public class WireMockConfiguration implements SmartLifecycle { WireMock.configureFor(new WireMock(this.server)); this.running = true; if (log.isDebugEnabled() && this.server.isRunning()) { - log.debug("Started WireMock at port [" + this.server.port() + "]. It has [" - + this.server.getStubMappings().size() + "] mappings registered"); + int httpPort = this.server.isRunning() ? this.server.port() : -1; + int httpsPort = this.server.isRunning() && this.server.getOptions().httpsSettings().enabled() + ? this.server.httpsPort() : -1; + log.debug("Server [" + this.server + "] is already running at http port [" + httpPort + "] / https port [" + + httpsPort + "]. It has [" + this.server.getStubMappings().size() + "] mappings registered"); } } diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockTestExecutionListener.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockTestExecutionListener.java index 05200771b6..4f69dcd628 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockTestExecutionListener.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockTestExecutionListener.java @@ -20,6 +20,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContext; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestContext; import org.springframework.test.context.support.AbstractTestExecutionListener; @@ -30,6 +31,7 @@ import org.springframework.test.context.support.AbstractTestExecutionListener; * @author Marcin Grzejszczak * @author Matt Garner * @author Waldemar Panas + * @author Fabian Winter * @since 1.2.6 */ public final class WireMockTestExecutionListener extends AbstractTestExecutionListener { @@ -91,7 +93,7 @@ public final class WireMockTestExecutionListener extends AbstractTestExecutionLi } private boolean annotationMissing(TestContext testContext) { - if (testContext.getTestClass().getAnnotationsByType(AutoConfigureWireMock.class).length == 0) { + if (AnnotationUtils.findAnnotation(testContext.getTestClass(), AutoConfigureWireMock.class) == null) { if (log.isDebugEnabled()) { log.debug( "No @AutoConfigureWireMock annotation found on [" + testContext.getTestClass() + "]. Skipping"); diff --git a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockAdditionalImportTests.java b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockAdditionalImportTests.java new file mode 100644 index 0000000000..ce091bc9d4 --- /dev/null +++ b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockAdditionalImportTests.java @@ -0,0 +1,144 @@ +/* + * Copyright 2013-2020 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 + * + * 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 org.springframework.cloud.contract.wiremock; + +import java.util.Collections; +import java.util.List; + +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.client.WireMock; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.stereotype.Component; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.reactive.function.client.WebClient; + +// issue 1541 +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = TestConfiguration.class, + properties = "base-url=http://localhost:${wiremock.server.port}") +@AutoConfigureWireMock(port = 0) +@Import(ExtraConfig.class) +public class AutoConfigureWireMockAdditionalImportTests { + + @AfterEach + public void resetWiremock() { + WireMock.reset(); + } + + @Nested + @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = TestConfiguration.class, + properties = "base-url=http://localhost:${wiremock.server.port}") + @AutoConfigureWireMock(port = 0) + class SecondControllerTest { + + @Test + void test(@Autowired WebTestClient webTestClient) { + // arrange + WireMock.stubFor(WireMock.get(WireMock.urlMatching("/find-all")).willReturn( + ResponseDefinitionBuilder.okForJson(Collections.singletonList(new TestItem("my-name"))))); + + // act + List responseBody = webTestClient.get().uri("find-all").exchange().expectStatus() + .is2xxSuccessful().expectBody(new ParameterizedTypeReference>() { + }).returnResult().getResponseBody(); + + // assert + Assertions.assertThat(responseBody.get(0).getName()).isEqualTo("my-name"); + } + + } + +} + +@Component +class ExtraConfig { + +} + +@Configuration(proxyBeanMethods = false) +@EnableAutoConfiguration +class TestConfiguration { + + @Bean + WebClient webClient(WebClient.Builder builder) { + return builder.build(); + } + + @Bean + TestController testController(WebClient webClient, @Value("${base-url}") String baseUrl) { + return new TestController(webClient, baseUrl); + } + +} + +@RestController +class TestController { + + private final WebClient webClient; + + private final String baseUrl; + + TestController(WebClient webClient, String baseUrl) { + this.webClient = webClient; + this.baseUrl = baseUrl; + System.out.println("Creating with URL [" + this.baseUrl + "] HASH [" + this.hashCode() + "]"); + } + + @GetMapping("find-all") + public Mono> findAll() { + System.out.println("Will send a request to [" + this.baseUrl + "] HASH [" + this.hashCode() + "]"); + return webClient.get().uri(baseUrl + "/find-all").retrieve() + .bodyToMono(new ParameterizedTypeReference>() { + }); + } + +} + +class TestItem { + + private String name; + + TestItem() { + } + + TestItem(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/MetaAnnotationWithResetAfterEachTestApplicationTests.java b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/MetaAnnotationWithResetAfterEachTestApplicationTests.java new file mode 100644 index 0000000000..63424228ba --- /dev/null +++ b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/MetaAnnotationWithResetAfterEachTestApplicationTests.java @@ -0,0 +1,104 @@ +/* + * Copyright 2013-2020 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 + * + * 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 org.springframework.cloud.contract.wiremock; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +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; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.BDDAssertions.then; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = WiremockTestsApplication.class, properties = { + "app.baseUrl=http://localhost:${wiremock.server.port}", "wiremock.reset-mappings-after-each-test=true" }, + webEnvironment = WebEnvironment.NONE) +@ComponentTestAnnotation +@FixMethodOrder +@DirtiesContext +public class MetaAnnotationWithResetAfterEachTestApplicationTests { + + @Autowired + private WireMockServer wireMockServer; + + @Value("localhost:${wiremock.server.port}") + private String hostname; + + @Test + public void _01_test() throws Exception { + this.wireMockServer + .givenThat(WireMock.get("/should_register_mapping").willReturn(WireMock.aResponse().withBody("bar"))); + + String result = new RestTemplate().getForObject("http://" + this.hostname + "/should_register_mapping", + String.class); + + then(result).isEqualTo("bar"); + } + + @Test + public void _02_test() throws Exception { + String result = new RestTemplate().getForObject("http://" + this.hostname + "/should_register_mapping", + String.class); + + // taken from test/resources/mappings/resource-without-content-type.json + then(result).isEqualTo("Hello World"); + } + + @Test + public void _03_test() throws Exception { + WireMock.givenThat(WireMock.get("/should_register_mapping").willReturn(WireMock.aResponse().withBody("bar"))); + + String result = new RestTemplate().getForObject("http://" + this.hostname + "/should_register_mapping", + String.class); + + then(result).isEqualTo("bar"); + } + + @Test + public void _04_test() throws Exception { + String result = new RestTemplate().getForObject("http://" + this.hostname + "/should_register_mapping", + String.class); + + // taken from test/resources/mappings/resource-without-content-type.json + then(result).isEqualTo("Hello World"); + } + +} + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@SpringBootTest +@AutoConfigureWireMock(port = 0) +@interface ComponentTestAnnotation { + +}