Resolving port already in use in a Spring boot test DEFINED PORT

ahjashish picture ahjashish · Feb 15, 2017 · Viewed 19.7k times · Source

I have a spring boot application that fires up and executes a class that listens to Application Ready event to call an external service to fetch some data and then use that data to push some rules to the classpath for execution. For local testing we have mocked the external service within our application which works fine during the application startup.

The issue is while testing the application by running it with spring boot test annotation and embedded jetty container either on :

  • RANDOM PORT
  • DEFINED PORT

In case of RANDOM PORT, at the application startup, it picks up the url for the mock service from the properties file at a defined port and has no clue where the embedded container is running since it is randomly picked up, hence failing to give response.

In case of DEFINED PORT, for the first test case file it runs successfully, but the moment next file is picked up, it fails saying the port is already in use.

The test cases are partitioned logically in multiple files and need the external service to be called before the container starts to load the rules.

How can I either share the embedded container between test files in case of using defined port or refactor my application code instead to get hold of the random port while starting up during the test case execution.

Any help would be appreciated.

Application Startup code :

@Component
public class ApplicationStartup implements ApplicationListener<ApplicationReadyEvent> {

@Autowired
private SomeService someService;

@Override
public void onApplicationEvent(ApplicationReadyEvent arg0) {

    try {
        someService.callExternalServiceAndLoadData();
    }
    catch (Execption e) {}
    }
 }

Test Code Annotations: Test1

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@TestPropertySource("classpath:test-application.properties")
public class Test1 {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void tc1() throws IOException {.....}

Test Code Annotations: Test2

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@TestPropertySource("classpath:test-application.properties")
public class Test2 {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void tc1() throws IOException {.....}

Answer

Joel Neukom picture Joel Neukom · May 18, 2018

If you insist on using the same port on multiple test, you can prevent spring from caching the context for further tests by annotating your testclass with: @DirtiesContext

In your case:

@RunWith(SpringRunner.class)
@DirtiesContext
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@TestPropertySource("classpath:test-application.properties")

Here is a quote from Andy Wilkinson from his answer on this discussion

This is working as designed. Spring Framework's test framework will, by default, cache contexts for possible reuse by multiple test classes. You have two tests with different configuration (due to @TestPropertySource) so they will use different application contexts. The context for the first test will be cached and kept open while the second test is running. Both tests are configured to use the same port for Tomcat's connector. As a result, when the second test is run, the context fails to start due to a port clash with the connector from the first test. You have a few options:

  1. Use RANDOM_PORT
  2. Remove @TestPropertySource from Test2 so that the contexts have identical configuration and the context from the first test can be reused for the second test.
  3. Use @DirtiesContext so that the context isn't cached