Set property with wiremock random port in spring boot test
Asked Answered
H

9

22

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.

Hawsehole answered 9/2, 2018 at 14:2 Comment(0)
L
10

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.

Landy answered 19/2, 2018 at 4:30 Comment(1)
This is exactly what I was looking for! I knew that there should be a standard solution for such a common problem!Hawsehole
C
13

The property name mentioned in https://mcmap.net/q/573861/-set-property-with-wiremock-random-port-in-spring-boot-test (i.e. wiremock.port) is not correct, at least since Spring Cloud Contract version 2.1.2.RELEASE.

1. Working example

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+");
    }

}

2. Other WireMock properties

Other than wiremock.server.port, @AutoConfigureWireMock populates the environment with some other properties too:

  1. wiremock.server.https-port
  2. wiremock.server.stubs[]
  3. wiremock.server.files[]

3. Gradle dependencies

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}'

4. Using in application.yaml files

If 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")));
    }
}
Criseldacrisey answered 23/6, 2019 at 12:32 Comment(2)
Is there any way to create multiple wiremock instances when we are using @AutoConfigureWiremock annotation? I could not be successfully run tests in parallel with AutoConfigureWiremock annotation in JUnit 5 Spring-boot tests.Headcloth
Is it possible to do this without @SpringBootTest? I'm trying to write a unit test with just @ExtendWith(SpringExtension.class)Crook
L
10

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.

Landy answered 19/2, 2018 at 4:30 Comment(1)
This is exactly what I was looking for! I knew that there should be a standard solution for such a common problem!Hawsehole
H
10

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.

Hwang answered 16/1, 2019 at 12:21 Comment(2)
This won't work if the wiremock.server.port is only defined at runtime.Hawsehole
Will alter my answer to include a prerequisiteHwang
H
4

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.

Hawsehole answered 19/2, 2018 at 12:24 Comment(0)
F
2

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)

Francinefrancis answered 23/1, 2023 at 21:27 Comment(0)
D
2

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.

Dilative answered 6/3, 2023 at 12:33 Comment(0)
B
1

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 ;)

Biserrate answered 13/2, 2018 at 14:17 Comment(0)
L
0

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());
Longmire answered 13/2, 2018 at 15:7 Comment(0)
S
0
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();
}
Slingshot answered 19/3, 2023 at 14:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.