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,

+3


source to share


1 answer


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.

+3


source







All Articles