How to find port of Spring Boot container when running a spock test using property server.port=0

12,486

Solution 1

You can find the port using this code:

int port = context.embeddedServletContainer.port

Which for those interested in the java equivalent is:

int port = ((TomcatEmbeddedServletContainer)((AnnotationConfigEmbeddedWebApplicationContext)context).getEmbeddedServletContainer()).getPort();

Here's an abstract class that you can extends which wraps up this initialization of the spring boot application and determines the port:

abstract class SpringBootSpecification extends Specification {

    @Shared
    @AutoCleanup
    ConfigurableApplicationContext context

    int port = context.embeddedServletContainer.port

    void launch(Class clazz) {
        Future future = Executors.newSingleThreadExecutor().submit(
                new Callable() {
                    @Override
                    public ConfigurableApplicationContext call() throws Exception {
                        return (ConfigurableApplicationContext) SpringApplication.run(clazz)
                    }
                })
        context = future.get(20, TimeUnit.SECONDS);
    }
}

Which you can use like this:

class MySpecification extends SpringBootSpecification {
    void setupSpec() {
        launch(MyLauncher.class)
    }

    String getBody(someParam) {
        ResponseEntity entity = new RestTemplate().getForEntity("http://localhost:${port}/somePath/${someParam}", String.class)
        return entity.body;
    }
}

Solution 2

The injection will work with Spock, as long as you've configured your spec class correctly and have spock-spring on the classpath. There's a limitation in Spock Spring which means it won't bootstrap your Boot application if you use @SpringApplicationConfiguration. You need to use @ContextConfiguration and configure it manually instead. See this answer for the details.

The second part of the problem is that you can't use a GString for the @Value. You could escape the $, but it's easier to use single quotes:

@Value('${local.server.port}')
private int port;

Putting this together, you get a spec that looks something like this:

@ContextConfiguration(loader = SpringApplicationContextLoader, classes = SampleSpockTestingApplication.class)
@WebAppConfiguration
@IntegrationTest("server.port=0")
class SampleSpockTestingApplicationSpec extends Specification {

    @Value("\${local.server.port}")
    private int port;

    def "The index page has the expected body"() {
        when: "the index page is accessed"
        def response = new TestRestTemplate().getForEntity(
            "http://localhost:$port", String.class);
        then: "the response is OK and the body is welcome"
        response.statusCode == HttpStatus.OK
        response.body == 'welcome'
    }
}

Also note the use of @IntegrationTest("server.port=0") to request a random port be used. It's a nice alternative to configuring it in application.properties.

Share:
12,486
Bohemian
Author by

Bohemian

Self-proclaimed SQL guru, Java pro and regex fan... and currently elected moderator. In all code, I strive for brevity (without loss of legibility). The less code there is, the less places there are for bugs to lurk. While contributing here, I have learned that say not that you know something until you try to teach it. My real name is Glen Edmonds. If one of my posts "changed your life", you can donate to my Liberapay account. FYI, that cute animal I use for my avatar is a wombat.

Updated on June 17, 2022

Comments

  • Bohemian
    Bohemian almost 2 years

    Given this entry in application.properties:

    server.port=0
    

    which causes Spring Boot to chose a random available port, and testing a spring boot web application using spock, how can the spock code know which port to hit?

    Normal injection like this:

    @Value("${local.server.port}")
    int port;
    

    doesn't work with spock.

  • Gilad Bar Orion
    Gilad Bar Orion over 8 years
    @SpringApplicationConfiguration seems to be working using Spock 1.0.
  • Jacomoman
    Jacomoman almost 7 years
    Andy's answer should've been marked as the real answer. His solution for getting port# with the @Value string is much more elegant.
  • Jacomoman
    Jacomoman almost 7 years
    BTW, @andy-wilkinson, spock-spring now supports @SpringBootTest as a single annotation instead of having to combine @ContextConfiguration, @WebAppConfiguration, and @IntegrationTests. So all you have to do is: @SpringBootTest (webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)