11

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.

Nicola Ambrosetti
  • 2,459
  • 3
  • 20
  • 31

6 Answers6

6

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.

βξhrαng
  • 41,698
  • 21
  • 103
  • 145
Dagon
  • 156
  • 11
5

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.

Nicola Ambrosetti
  • 2,459
  • 3
  • 20
  • 31
5

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.

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")));
    }
}
βξhrαng
  • 41,698
  • 21
  • 103
  • 145
4

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.

0

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

Peter Thomas
  • 40,008
  • 10
  • 46
  • 142
-1

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());
redhunter
  • 47
  • 6