Feeling very stupid, but I'm not able to test an endpoint in Spring Boot (version 2.7.1
) with JUnit 5.
Briefly, I want to test the real endpoint response, so I've created a test class like explained in Testing the Web Layer. Herein the code:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ApiDocumentationControllerIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
public void getOpenApiDocumentationShouldReturnOk() {
assertThat(restTemplate.getForEntity("/api-docs", String.class).getStatusCode())
.isEqualTo(HttpStatus.OK);
}
}
But when I run the test, TestRestTemplate
calls http://localhost:8080/api-docs
ignoring the fact that server should be listen to a random port.
What am I missing? As other examples suggest, I've tried to add:
@LocalServerPort
private int randomServerPort;
But in this case I have an exception during the launch of the test:
java.lang.IllegalArgumentException: Could not resolve placeholder 'local.server.port' in value "${local.server.port}"
I've tried to set 0
as port —that should be considered a random port by the framework— with no success. Spring complains it cannot listen localhost:0
.
Service configuration is empty (AKA application.yaml
is empty and I didn't set params in other ways), so all the configuration values are the default ones by Spring Boot.
Probably it is a problem for dummies but since yesterday I'm looking for a solution but I didn't find it.
More details
Controller
@Slf4j
@Controller
public class ApiDocumentationController {
private final Resource resourceFile;
private final ObjectMapper yamlReader;
private final ObjectMapper jsonWriter;
public ApiDocumentationController(@Value("classpath:openapi/api-documentation.yaml") Resource resourceFile,
@Qualifier("yamlReader") ObjectMapper yamlReader,
ObjectMapper objectMapper) {
this.resourceFile = resourceFile;
this.yamlReader = yamlReader;
this.jsonWriter = objectMapper;
}
@GetMapping(value = "/api-docs", produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<String> getOpenApiDocumentation() {
return Try.of(() -> yamlReader.readValue(resourceFile.getInputStream(), Object.class))
.mapTry(jsonWriter::writeValueAsString)
.map(apiDocumentation -> ResponseEntity.status(HttpStatus.OK).body(apiDocumentation))
.get(); // FIXME This forced Try::get is ugly
}
}
Launcher
@SpringBootApplication
public class AirportTravellersInsightsServiceApplication {
public static void main(String[] args) {
SpringApplication.run(AirportTravellersInsightsServiceApplication.class, args);
}
Gradle build
(This is an excerpt of build.gradle
.)
Test mentioned above is in integrationTest
source set.
plugins {
id 'org.springframework.boot' version '2.7.1'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
id 'jacoco'
id 'checkstyle'
id 'idea'
}
group = 'example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
ext.versions = [
checkstyleVersion: "8.39",
vavrVersion: "0.10.4"
]
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation "io.vavr:vavr:${versions.vavrVersion}"
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml'
// TEST
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.junit.jupiter:junit-jupiter-api'
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
testAnnotationProcessor 'org.projectlombok:lombok'
}
sourceSets {
integrationTest {
compileClasspath += sourceSets.main.output
compileClasspath += sourceSets.test.output
runtimeClasspath += sourceSets.main.output
runtimeClasspath += sourceSets.test.output
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
integrationTestImplementation.extendsFrom testImplementation
integrationTestRuntimeOnly.extendsFrom runtimeOnly
implementation {
exclude module: 'spring-boot-starter-tomcat'
}
}
tasks.named('test') {
useJUnitPlatform()
}
task integrationTest(type: Test, description: 'Runs integration tests.', group: LifecycleBasePlugin.VERIFICATION_GROUP) {
useJUnitPlatform()
testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath
shouldRunAfter test
}
check.dependsOn integrationTest