Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running integration tests for a spring-boot REST service using gradle

I am currently trying to setup integration test framework for a REST service which is built on:

  1. Spring-boot
  2. Gradle
  3. Jetty

I was able to use spring-boot integration test framework along with spring-boot junit runner to bring up the app context and run the tests successfully.

The next thing that I was trying to do was to have a gradle task which will do the following:

  1. Build the jar(not war)
  2. Start jetty and deploy the jar
  3. Run a set of test-cases against this jar.
  4. Stop jetty

=> I tried using the 'jetty' plugin. But it does not seem to be supporting jar files.
=> I then tried using the JavaExec task to run the jar and then run the tests, but then I couldn't find a straight-forward way to stop the jar process after the tests are done.
=> The same issue with the Exec type task.

So, I have two questions regarding this:

  1. Is there a way to achieve the above said form of integration testing using gradle.

  2. Is this way of integration-testing recommended or is there a better way of doing it?

Any thoughts and insights are much appreciated.

Thanks,

like image 915
Balasubramanian Avatar asked Oct 28 '25 16:10

Balasubramanian


1 Answers

There are different ways to achieve what you want. The approach I helped with at a client relied on the /shutdown URL provided by Spring Boot Actuator. Important If you use this approach, be sure to either disable or secure the /shutdown endpoint for production.

Within the 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 should make sure that your integration tests depend on the startWebApp tasks and they should be finalised by the stop task. So something like this:

integTest.dependsOn "startWebApp"
integTest.finalizedBy "stopWebApp"

Of course, you need to create the custom task implementations too:

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's important to start the server on a thread, otherwise the task never ends. The task to stop the server is more straightforward:

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.

like image 54
Peter Ledbrook Avatar answered Oct 31 '25 05:10

Peter Ledbrook



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!