Running integration tests for spring-boot REST service using gradle
I am currently trying to set up an integration testing framework for a REST service which is built on:
- Spring-boot
- Gradle
- Jetty
I was able to use Spring-boot integration test along with Spring-boot junit runner to invoke application context and run tests successfully.
The next thing I tried to do was a gradle task that will do the following:
- Build a bank (not a war)
- Run the pier and unfold the can
- Run a set of test cases against this jar.
- Stop dock
=> I tried using the jetty plugin . But it doesn't seem to support jar files.
=> Then I tried to use JavaExec task to start the jar and then run the tests, but then I couldn't find a direct way to stop the jar process after the tests finished.
=> Same problem with a task like Exec.
So, I have two questions:
-
Is there a way to achieve the above form of integration testing using gradle.
-
Is this integration testing method recommended, or is there a better way to do it?
Any thoughts and ideas are greatly appreciated.
Thank,
source to share
There are different ways to achieve what you want. The approach I helped the client with was based on the / shutdown url provided by the Spring Boot Actuator. Important . If you are using this approach, make sure to disable or secure the / shutdown endpoint for production.
In your build file, you have two tasks:
task startWebApp(type: StartApp) {
dependsOn 'assemble'
jarFile = jar.archivePath
port = 8080
appContext = "MyApp"
}
task stopWebApp(type: StopApp) {
urlPath = "${startWebApp.baseUrl}/shutdown"
}
You need to make sure your integration tests are task specific startWebApp
and they should be completed with a stop task. So something like this:
integTest.dependsOn "startWebApp"
integTest.finalizedBy "stopWebApp"
Of course, you also need to create your own task implementations:
class StartApp extends DefaultTask {
static enum Status { UP, DOWN, TIMED_OUT }
@InputFile
File jarFile
@Input
int port = 8080
@Input
String appContext = ""
String getBaseUrl() {
return "http://localhost:${port}" + (appContext ? '/' + appContext : '')
}
@TaskAction
def startApp() {
logger.info "Starting server"
logger.debug "Application jar file: " + jarFile
def args = ["java",
"-Dspring.profiles.active=dev",
"-jar",
jarFile.path]
def pb = new ProcessBuilder(args)
pb.redirectErrorStream(true)
final process = pb.start()
final output = new StringBuffer()
process.consumeProcessOutputStream(output)
def status = Status.TIMED_OUT
for (i in 0..20) {
Thread.sleep(3000)
if (hasServerExited(process)) {
status = Status.DOWN
break
}
try {
status = checkServerStatus()
break
}
catch (ex) {
logger.debug "Error accessing app health URL: " + ex.message
}
}
if (status == Status.TIMED_OUT) process.destroy()
if (status != Status.UP) {
logger.info "Server output"
logger.info "-------------"
logger.info output.toString()
throw new RuntimeException("Server failed to start up. Status: ${status}")
}
}
protected Status checkServerStatus() {
URL url = new URL("$baseUrl/health")
logger.info("Health Check --> ${url}")
HttpURLConnection connection = url.openConnection()
connection.readTimeout = 300
def obj = new JsonSlurper().parse(
connection.inputStream,
connection.contentEncoding ?: "UTF-8")
connection.inputStream.close()
return obj.status == "UP" ? Status.UP : Status.DOWN
}
protected boolean hasServerExited(Process process) {
try {
process.exitValue()
return true
} catch (IllegalThreadStateException ex) {
return false
}
}
}
Note that it is important to start the server in a thread, otherwise the task will never end. The task of stopping the server is simpler:
class StopApp extends DefaultTask {
@Input
String urlPath
@TaskAction
def stopApp(){
def url = new URL(urlPath)
def connection = url.openConnection()
connection.requestMethod = "POST"
connection.doOutput = true
connection.outputStream.close()
connection.inputStream.close()
}
}
It basically sends an empty POST to the / shutdown url to stop the running server.
source to share