How to find port of Spring Boot container when running a spock test using property server.port=0
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
.
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, 2022Comments
-
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 over 8 years@SpringApplicationConfiguration seems to be working using Spock 1.0.
-
Jacomoman almost 7 yearsAndy's answer should've been marked as the real answer. His solution for getting port# with the @Value string is much more elegant.
-
Jacomoman almost 7 yearsBTW, @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)