Enterprise Java

Chicken and egg – resolving Spring properties ahead of a test

Consider a service class responsible for making a remote call and retrieving a detail:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
...
public class CitiesService {
    private final WebClient.Builder webClientBuilder;
    private final String baseUrl;
 
    public CitiesService(
            WebClient.Builder webClientBuilder,
            @Value("${cityservice.url}") String baseUrl) {
        this.webClientBuilder = webClientBuilder;
        this.baseUrl = baseUrl;
    }
 
 
    public Flux<City> getCities() {
        return this.webClientBuilder.build()
                .get()
....

This is a Spring Bean and resolves the url to call through a property called “cityservice.url”.

If I wanted to test this class, an approach that I have been using when using WebClient is to start a mock server using the excellent Wiremock and using it to test this class. A Wiremock mock looks like this:

01
02
03
04
05
06
07
08
09
10
11
12
private static final WireMockServer WIREMOCK_SERVER =
            new WireMockServer(wireMockConfig().dynamicPort());
 
 
    .....
 
    WIREMOCK_SERVER.stubFor(get(urlEqualTo("/cities"))
                .withHeader("Accept", equalTo("application/json"))
                .willReturn(aResponse()
                        .withStatus(200)
                        .withHeader("Content-Type", "application/json")
                        .withBody(resultJson)));

The Wiremock server is being started up at a random port and it is set to respond to an endpoint called “/cities”. Here is where the chicken and egg problem comes up:

1. The CitiesService class requires a property called “cityservice.url” to be set before starting the test.

2. Wiremock is started at a random port and the url that it is responding to is “http://localhost:randomport” and is available only once the test is kicked off.

There are three potential solutions that I can think of to break this circular dependency:

Approach 1: To use a hardcoded port

This approach depends on starting up Wiremock on a fixed port instead of a dynamic port, this way the property can be set when starting the test up, something like this:

1
2
3
4
5
6
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = CitiesServiceHardcodedPortTest.SpringConfig.class,
        properties = "cityservice.url=http://localhost:9876")
public class CitiesServiceHardcodedPortTest {
    private static final WireMockServer WIREMOCK_SERVER =
            new WireMockServer(wireMockConfig().port(9876));

Here Wiremock is being started at port 9876 and the property at startup is being set to “http://localhost:9876/”.

This solves the problem, however, this is not CI server friendly, it is possible for the ports to collide at runtime and this makes for a flaky test.

Approach 2: Not use Spring for test

A better approach is to not use the property, along these lines:

01
02
03
04
05
06
07
08
09
10
11
12
public class CitiesServiceDirectTest {
    private static final WireMockServer WIREMOCK_SERVER =
            new WireMockServer(wireMockConfig().dynamicPort());
 
    private CitiesService citiesService;
 
    @BeforeEach
    public void beforeEachTest() {
        final WebClient.Builder webClientBuilder = WebClient.builder();
 
        this.citiesService = new CitiesService(webClientBuilder, WIREMOCK_SERVER.baseUrl());
    }

Here the service is being created by explicitly setting the baseUrl in the constructor, thus avoiding the need to set a property ahead of the test.

Approach 3: Application Context Initializer

ApplicationContextInitializer is used for programmatically initializing a Spring Application Context and it can be used with a test to inject in the property before the actual test is executed. Along these lines:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = CitiesServiceSpringTest.SpringConfig.class)
@ContextConfiguration(initializers = {CitiesServiceSpringTest.PropertiesInitializer.class})
public class CitiesServiceSpringTest {
    private static final WireMockServer WIREMOCK_SERVER =
            new WireMockServer(wireMockConfig().dynamicPort());
 
    @Autowired
    private CitiesService citiesService;
 
    @Test
    public void testGetCitiesCleanFlow() throws Exception {
        ...
    }
 
 
 
    static class PropertiesInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
 
        @Override
        public void initialize(ConfigurableApplicationContext applicationContext) {
            TestPropertyValues.of(
                    "cityservice.url=" + "http://localhost:" + WIREMOCK_SERVER.port()
            ).applyTo(applicationContext.getEnvironment());
        }
    }
 
}

Wiremock is started up first, then Spring context is initialized using the initializer which injects in the “cityservice.url” property using the Wiremocks dynamic port, this way the property is available for wiring into CityService.

Conclusion

I personally prefer Approach 2, however it is good to have Spring’s wiring and the dependent beans created ahead of the test and if the class utilizes these then I prefer Approach 3. Application Context initializer provides a good way to break the chicken and egg problem with properties like these which need to be available ahead of Spring’s context getting engaged.

All the code samples are available here:

Approach 1: https://github.com/bijukunjummen/reactive-cities-demo/blob/master/src/test/java/samples/geo/service/CitiesServiceHardcodedPortTest.java

Approach 2: https://github.com/bijukunjummen/reactive-cities-demo/blob/master/src/test/java/samples/geo/service/CitiesServiceDirectTest.java

Approach 3: https://github.com/bijukunjummen/reactive-cities-demo/blob/master/src/test/java/samples/geo/service/CitiesServiceSpringTest.java

Published on Java Code Geeks with permission by Biju Kunjummen, partner at our JCG program. See the original article here: Chicken and egg – resolving Spring properties ahead of a test

Opinions expressed by Java Code Geeks contributors are their own.

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
helospark
helospark
4 years ago

Another option I prefer is to use Spring Cloud Wiremock (org.springframework.cloud:spring-cloud-contract-wiremock).
Just annotate your test class with AutoConfigureWireMock.

@ExtendWith(SpringExtension.class)
@AutoConfigureWireMock(port = DYNAMIC_PORT, stubs = {“classpath:/stubs/…”})
@SpringBootTest(…, properties = “cityservice.url=http://localhost:${wiremock.server.port}”)
public class CitiesServiceSpringTest {

}

Spring then have access to the dynamic port via ${wiremock.server.port}, so you could use that in your TestPropertySource (or properties file).
You will also have a WiremockServer in the application context you can inject into your test class if needed.

Back to top button