September 15, 2016 · java gradle unit testing integration testing

Separating Unit from Integration tests in Java using Gradle

Having spent some significant time in the Ruby community and finding a new found appreciation for clean unit and integration tests, it often befuddles me why there isn't such a clean separation of test responsibility and scope in other languages.

Java has learned a lot from other test frameworks over the last decade. The venerable Junit test framework has matured significantly but with the addition of fluent libraries such as RestAssured, simple mocking frameworks such as Mockito, and verbose matching capabilities such as Hamcrest, to name a few, it's now possible to write tests with properties matching many other languages touted for readability and focused test intent.

So having established that Java has some really great testing libraries, that doesn't address how to use them and this is part of the problem. There is no simple way to separate unit test from integration test in Java so why should there be a clean understanding of what a unit or integration test is? Often times I see purported unit tests in Junit that are truely integration tests requiring a database and a full application stack to be stood up with no sign of a true unit test in sight.

Very simply stated;

The design, purpose and intent of unit and integration tests is the subject of a much larger discussion and outside the scope of this post.

So back to the problem at hand. Having a desire to separate fast running unit tests from integration tests I have struggled for an answer until I came across the gradle-testsets-plugin and these posts from Petri kainulainen, here and here

I would strongly recommend reading both posts, but for brevity, here are the main mechanics and some further tips beyond.

Step 1.

Include jcenter as a source for your build script dependencies and pull in the gradle-testsets-plugin dependency

buildscript {  
  repositories {
    jcenter()
  }
  dependencies {
    classpath 'org.unbroken-dome.gradle-plugins:gradle-testsets-plugin:1.0.2'
  }
}

Step 2.

Apply the plugin to the build. Be sure to activate this after the java plugin and before any plugins which may build off the gradle tasks automatically created by the plugin.

apply plugin: 'org.unbroken-dome.test-sets'  

Step 3.

Create the new test set definition and configuration. Here we want to add an integration test suite but this could be any category of tests you wish to scope together.

testSets {  
  integrationTest
}

Ensure that the check step executes the new test definition and that the new integrationTest step runs after the normal test (unit) step.

check.dependsOn integrationTest  
integrationTest.mustRunAfter test  

Ensure that integration tests are always run regardless if they passed on previous runs

project.integrationTest {  
  outputs.upToDateWhen { false }
}

Finally ensure that the output for tasks of type Test are namespaced appropriately so reports are separated for the test (unit) and integrationTest tasks

tasks.withType(Test) {  
  reports.html.destination = file("${reporting.baseDir}/${name}")
}

Step 4.

Test compile dependencies should be reviewed and the new integrationTestCompile dependencies declared appropriately
e.g.

testCompile("junit:junit")  
integrationTestCompile("org.springframework.boot:spring-boot-starter-test",  
                       "com.jayway.restassured:json-path:2.8.0",
                       "com.jayway.restassured:rest-assured:2.8.0",
                       "com.jayway.restassured:spring-mock-mvc:2.8.0",
                       "com.jayway.restassured:xml-path:2.8.0")

Step 5.

Restructure your test file layout. Your directory structure should look something like the following.

src/  
  main/
    java/...
    resources/...
  integrationTest/
    java/...
    resources/...
  test/
    java/...
    resources/...

At this point you should be able to run gradle clean build and see your separate test and integrationTest related tasks execute.

Real time test reporting

To see a visual report of test execution and outcome as it happens in the console, add the following

test {  
  afterTest { desc, result ->
    println "Executing test [${desc.className}].${desc.name} with result: ${result.resultType}"
    }
}
integrationTest {  
  afterTest { desc, result ->
    println "Executing test [${desc.className}].${desc.name} with result: ${result.resultType}"
    }
}

Test Coverage

I use Jacoco for test coverage with the help of the Jacoco gradle plugin. While it would be ideal to have separate test coverage for integration and unit test suites in separate reports I was unable to find a simple method to generate them independently. However you can combine the coverage from both suites with the following;

apply plugin: 'jacoco'  
.....
jacoco {  
    toolVersion = "0.7.5.201505241946"
}

jacocoTestReport {  
    reports {
        xml.enabled false
        csv.enabled false
        html{
            enabled true
            destination "${buildDir}/reports/jacoco"
        }
    }
    executionData(test, integrationTest)
}

tasks.build.dependsOn(jacocoTestReport)

I hope this post has proved useful. The separation of test types has many benefits including;