Reputation: 2705
I have a Spring Boot test that uses wiremock to mock an external service. In order to avoid conflicts with parallel builds I don't want to set a fixed port number for wiremock and would like to rely on its dynamic port configuration.
The application uses a property (external.baseUrl
) set in the application.yml (under src/test/resources). However I didn't find a way to programmatically override that. I've tried something like this:
WireMockServer wireMockServer = new WireMockServer();
wireMockServer.start();
WireMock mockClient = new WireMock("localhost", wireMockServer.port());
System.setProperty("external.baseUrl", "http://localhost:" + wireMockServer.port());
but it didn't work and the value in application.yml was used instead. All other solutions that I've looked at override the property with a static value (for example in some annotation), but I don't know the value of the wiremock port until the test is run.
Clarification:
Both spring boot and wiremock run on random ports. That's fine and I know how to get the value of both ports. However wiremock is supposed to mock an external service and I need to tell my application how to reach it. I do this with the external.baseUrl
property. The value I want to set in my test depends of course on the wiremock port number. My problem is simply how to programmatically set a property in a spring boot test.
Upvotes: 23
Views: 59080
Reputation: 165
Thanks to brauls, refereeing to his repo, how feign client to make a request using wiremock, Junit 5 for testing with a random port
Git url --> https://github.com/brauls/spring-feign-wiremock-example
Add below entry in pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-wiremock</artifactId>
<version>${spring-cloud-contract-wiremock.version}</version>
</dependency>
Add below entry in application-test.yaml
client:
endpoint: http://localhost:${wiremock.server.port}/
Add below entry in all the junit classes
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
@AutoConfigureWireMock(port = 0)
Add below method to mock the api respons in your junit classes where ever needed
@BeforeEach
void setUp() throws IOException {
setupMockBooksResponse();
}
private String setupMockBooksResponse() throws IOException {
File mockResposeFile = ResourceUtils.getFile("classpath:" + "mocks/sample_stub.json");
String content = "convert file content it into xml\json";
ObjectMapper om = new ObjectMapper();
String jsonInString = om.writeValueAsString("yourpojocloass");
return stubFor(post("/api/v2/")
.withHeader("Content-Type", equalTo("application/json"))
.withHeader("Accept", equalTo("application/json"))
.withRequestBody(WireMock.equalToJson(jsonInString))
.willReturn(WireMock.aResponse()
.withStatus(HttpStatus.OK.value())
.withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.withBody(content))).toString();
}
Upvotes: 1
Reputation: 838
In my use case (not using SpringBootTest
annotation but SpringJUnitConfig
) the property wiremock.server.port
is valued to 0
.
Instead, using the SpEL #{wireMockServer.port}
worked correctly.
Upvotes: 2
Reputation: 91
When you use org.springframework.cloud:spring-cloud-contract-wiremock dependency, if you are a fan of annotation, you can just add @AutoConfigureWireMock(port = Options.DYNAMIC_PORT)
Upvotes: 2
Reputation: 47933
The property name mentioned in https://stackoverflow.com/a/48859553/309683 (i.e. wiremock.port
) is not correct, at least since Spring Cloud Contract version 2.1.2.RELEASE
.
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock;
import org.springframework.core.env.Environment;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
@AutoConfigureWireMock(port = 0)
public class PortServiceTest {
@Autowired
private Environment environment;
@Test
public void shouldPopulateEnvironmentWithWiremockPort() {
assertThat(environment.containsProperty("wiremock.server.port")).isTrue();
assertThat(environment.getProperty("wiremock.server.port")).matches("\\d+");
}
}
Other than wiremock.server.port
, @AutoConfigureWireMock
populates the environment with some other properties too:
wiremock.server.https-port
wiremock.server.stubs[]
wiremock.server.files[]
To use Spring Cloud Contract WireMock in a Gradle based project, add the following dependency to your project:
testImplementation 'org.springframework.cloud:spring-cloud-contract-wiremock:${version}'
application.yaml
filesIf you configure your test application.yaml
file like this:
sample:
port: ${wiremock.server.port}
And define the following beans:
@Component
@ConfigurationProperties(prefix = "sample")
@Data
public class PortProperties {
private Integer port;
}
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class PortService {
private final PortProperties config;
public Integer getPort() {
return config.getPort();
}
}
You can verify that sample.port
is set to the randomly chosen wiremock port:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
@AutoConfigureWireMock(port = 0)
public class PortServiceTest {
@Autowired
private Environment environment;
@Autowired
private PortService portService;
@Test
public void shouldReturnWireMockPort() {
assertThat(portService.getPort())
.isNotNull()
.isEqualTo(Integer.parseInt(environment.getProperty("wiremock.server.port")));
}
}
Upvotes: 15
Reputation: 196
Consider using Spring Cloud Contract Wiremock
There is already a JUnit Rule builder which allows to specify ${wiremock.port}
to set random port in property/yaml files
Or you can use WireMockRestServiceServer
to bind WireMock to your RestTemplate
so you don't even need to override URLs in your tests.
Upvotes: 10
Reputation: 121
Use property substitution in your application.properties:
external.baseUrl=http://exampleUrl:${wiremock.server.port}
This requires the wiremock.server.port
property to be set before the SpringBootTest is initialised, which can be achieved by adding the @AutoConfigureWireMock
annotation to your test class.
Upvotes: 11
Reputation: 2705
I could not find a way to override properties in a Spring Boot integration test, since the test is run only after the application is created and all the beans already configured.
As a work around I added a @TestConfiguration
to the test to replace the beans in the application:
private static WireMockServer wireMockServer1 = getWireMockServer();
private static WireMockServer wireMockServer2 = getWireMockServer();
private static WireMockServer wireMockServer3 = getWireMockServer();
private static WireMockServer getWireMockServer() {
final WireMockServer wireMockServer = new WireMockServer(options().dynamicPort());
wireMockServer.start();
return wireMockServer;
}
@TestConfiguration
static class TestConfig {
@Bean
@Primary
public BeanUsingAProperty1 getBean1() {
BeanUsingAProperty myBean = new BeanUsingAProperty();
myBean.setPort(wireMockServer.port());
return myBean;
}
@Bean
@Primary
public BeanUsingAProperty2 getBean2() {
String baseUrl = "http://localhost:" + wireMockServer2.port();
return new BeanUsingAProperty2(baseUrl);
}
@Bean
@Primary
public BeanUsingAProperty3 getBean3() {
String baseUrl = "http://localhost:" + wireMockServer3.port() + "/request";
return new BeanUsingAProperty3(new RestTemplate(), baseUrl, "someOtherParameter");
}
}
This effectively replaced the BeanUsingAProperty
with the one defined in the test so that it has the correct port number for Wiremock.
For this configuration to be picked up I had to add this class in the test annotation
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {
MySpringBootApplication.class, MyIntegrationTest.TestConfig.class })
Note that I use the non-static Wiremock API, since I have several such external services that each need to be mocked. Note that how the different beans are built is different depending on how each was designed.
Upvotes: 4
Reputation: 67
How are you reading external.baseUrl
?
If you are using a @Value
annotated property, you can use ReflectionTestUtils
to set the port after you have setup the mock server.
ReflectionTestUtils.setField(yourTestClass, "youPort", wireMockServer.port());
Upvotes: 0
Reputation: 58058
The approach I use to programmatically change a property when starting a Spring Boot app, is to pass the custom value into the application main entry-point String[]
args. This will have the effect of over-riding all other means such as System properties, YML or other config files.
Here is an example:
String[] args = new String[]{"--my.prop=foo"};
SpringApplication.run(Application.class, args);
It will be easy for you to expose a static method or custom API which starts the Spring Boot app (for testing) and with the value you want.
And then, once you have the value of the wiremock port - things are easy. Here is an example: PaymentServiceContractTest.java
P.S. Karate (the open-source test examples I am using above) is a new alternative to WireMock, do check it out ;)
Upvotes: 1