Executable acceptance tests using Spring Boot

In a continuous delivery context, you might want to reuse your acceptance tests from end to end:

  • to run them locally in a developer's environment,
  • in your continuous integration environment while building your application,
  • against a test server,
  • against even the production environment.

In order to achieve this, one cannot just execute them using the build technology all along, being either Maven, Gradle or any
other. You want your acceptance tests being executable, delivered as a proper artifact that can be reused along your
pipeline without having to rely on your SCM or running your build again.

In my context, I was using JUnit and Selenium to define my acceptance tests in a Gradle module. It is quite straightforward
to create an application (with a main method) that uses JUnitCore to run a predefined set of classes. Something like:

JUnitCore junit = new JUnitCore()
junit.run(MyTest, MyOtherTest)

I'll show in another post how this can be refined further.

Then, I was faced with the packaging and delivery of those acceptance tests. The tests rely on many dependencies (JUnit being
only one of them) and I need to package them together with the acceptance test module. There are technologies to do this but I was already creating a multi-module Spring Boot application, so I could just reuse the packaging feature of this great framework to help me!

First of all, I had to make my acceptance tests being able to be run as a Spring Boot application.

I decided to create an AcceptanceRunner interface in order to abstract the acceptance tests:

interface AcceptanceRunner {
	boolean run() throws Exception
}

Then, I just created a regular, non web, Spring Boot application:

@Configuration
@ComponentScan('net.nemerosa.ontrack.acceptance')
@EnableAutoConfiguration
class Start {
    static void main(String... args) {
        def ctx = SpringApplication.run(Start.class, args);
        def runners = ctx.getBeansOfType(AcceptanceRunner).values()
        boolean allOK = runners.collect { it -> it.run() }.every()
        if (allOK) {
            System.exit 0
        } else {
            System.exit 1
        }
    }
}

The code is simple enough to understand:

  1. first, we initialise the Spring Boot context and get the associated Spring application context,
  2. we get all runners from the application context,
  3. and run them,
  4. only when all of them are returning OK do we return a 0 exit code indicating success.

Now, I just had to register my JUnit AcceptanceRunner:

@Component
class JUnitAcceptanceRunner implements AcceptanceRunner {
	@Override
	boolean run() throws Exception {
		JUnitCore junit = new JUnitCore()
		junit.run(MyTest, MyOtherTest)
	}
}

Note that I used the AcceptanceRunner abstraction in order to allow some injection to occur in the runner instances (this will
prove being very useful - see later posts). The acceptance runner remains very crude but this will also be improved (see later
posts).

Now, I have a regular Spring Boot application whose sole purpose is to run some JUnit tests.

In order to package my application as a regular Spring Boot executable JAR file, I just had to adapt the Gradle file
of my acceptance tests module:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.1.8.RELEASE")
    }
}

dependencies {
	// testCompile ...
	// testRuntime ...
	// ...
}

apply plugin: 'groovy'
apply plugin: 'spring-boot'

jar {
    enabled = false
}

task testJar(type: Jar) {
    from sourceSets.test.output
}

artifacts {
    archives testJar
}

bootRepackage {
    mainClass = 'net.nemerosa.ontrack.acceptance.boot.Start'
    withJarTask = 'testJar'
    customConfiguration = 'testRuntime'
}
  1. First of all, I make sure to apply the spring-boot plug-in.
  2. The jar task is disabled since my acceptance tests are all in the src/test/groovy folder.
  3. I enable a testJar task that packages my tests and registers it as a deliverable. Note that I do not specify a classifier - I just want the associated JAR to be the main deliverable for my acceptance test module.
  4. Finally, I register the Spring Boot application. I refer to the testJar for the repackaging and use the testRuntime as the configuration in order to get the right dependencies.

That's mostly it. In the end, in my case, I get an ontrack-acceptance-VERSION.jar. I store the package, and I can
download it at any point in my pipeline and I can run it as a regular Spring Boot application:

java -jar ontrack-acceptance-VERSION.jar -Dontrack.url=<url to test>
if [ "$?" == "1"]
then
	echo Test failed.
else
	echo Test success.
fi

Now, this is not the end of the story...

  • The JUnit test selection is hard coded and not of real use in a real pipeline, where environment and context can play a role - this can be refined further using Spring and I'll explain this in another post
  • Finally, I'll also show later how to use this in the context of automated acceptance tests running against an application deployed as a Docker container

For the whole code, just have a look at the ontrack project.

This article is part of series of three about continuous delivery and automated acceptance tests using Docker and Spring Boot: