Running Integration Tests With Gradle – Spock Edition
After we have finished this lesson, we:
- Can add custom test sets to our Gradle build.
- Know how we can compile and run integration tests which use Spock Framework.
This lesson assumes that:
Watch the Lesson
The text version of the lesson is given in the following:
The Requirements of Our Gradle Build
The requirements of our Gradle build are:
- Our integration tests must have a separate source directory. The src/integration-test/groovy directory must contain the source code of our integration tests.
- Our integration tests must have a separate resource directory. The src/integration-test/resources directory must contain the resources of our integration tests.
- We must be able to run either unit tests, integration tests, or all tests.
- If an integration test fails, our build must fail as well.
- Integration and unit tests must have different HTML report directories.
Let’s start by adding a new test set to our Gradle build.
Adding Test Sets to Our Gradle Build
We can add new test sets to our Gradle build by using the Gradle TestSets plugin. We can use this plugin if we are also using the java and/or groovy plugin. We can apply the Gradle TestSets plugin by following these steps:
First, we have to declare the dependencies of our build script by following these steps:
- Configure Gradle to use the Bintray’s JCenter Maven repository when it resolves the dependencies of our build script.
- Add the Gradle TestSets plugin dependency (version 1.4.2) to the
classpath
configuration.
After we have configured the dependencies of our build script, our build.gradle file looks as follows:
buildscript { repositories { jcenter() } dependencies { classpath( 'org.unbroken-dome.gradle-plugins:gradle-testsets-plugin:1.4.2' ) } } apply plugin: 'groovy' repositories { mavenCentral() } dependencies { compile( 'org.slf4j:slf4j-api:1.7.25', 'org.slf4j:slf4j-log4j12:1.7.25', 'log4j:log4j:1.2.17' ) testCompile( 'junit:junit:4.12', 'org.codehaus.groovy:groovy-all:2.4.11', 'org.spockframework:spock-core:1.1-groovy-2.4' ) } test { useJUnit { includeCategories 'com.testwithspring.master.UnitTest' } testLogging { showStandardStreams = true } }
Second, we have to apply the Gradle TestSets plugin. After we have done this, our build.gradle file looks as follows:
buildscript { repositories { jcenter() } dependencies { classpath( 'org.unbroken-dome.gradle-plugins:gradle-testsets-plugin:1.4.2' ) } } apply plugin: 'groovy' apply plugin: 'org.unbroken-dome.test-sets' repositories { mavenCentral() } dependencies { compile( 'org.slf4j:slf4j-api:1.7.25', 'org.slf4j:slf4j-log4j12:1.7.25', 'log4j:log4j:1.2.17' ) testCompile( 'junit:junit:4.12', 'org.codehaus.groovy:groovy-all:2.4.11', 'org.spockframework:spock-core:1.1-groovy-2.4' ) } test { useJUnit { includeCategories 'com.testwithspring.master.UnitTest' } testLogging { showStandardStreams = true } }
If we are using Gradle 2.1 or newer, we can configure the dependencies of our build script and apply the required plugins by using the plugins DSL. If we decide to do so, we have to add the following snippet to our build.gradle file:
plugins { id 'groovy' id 'org.unbroken-dome.test-sets' version '1.4.2' }
Additional Reading:
Next, we have to configure the source and resource directories of our integration tests.
Configuring the Source and Resource Directories of Our Integration Tests
We can configure the source and resource directories of our integration tests by following these steps:
First, we have to add a new test set called integrationTest
to our Gradle build. After we have done this, our build.gradle file looks as follows:
buildscript { repositories { jcenter() } dependencies { classpath( 'org.unbroken-dome.gradle-plugins:gradle-testsets-plugin:1.4.2' ) } } apply plugin: 'groovy' apply plugin: 'org.unbroken-dome.test-sets' testSets { integrationTest } repositories { mavenCentral() } dependencies { compile( 'org.slf4j:slf4j-api:1.7.25', 'org.slf4j:slf4j-log4j12:1.7.25', 'log4j:log4j:1.2.17' ) testCompile( 'junit:junit:4.12', 'org.codehaus.groovy:groovy-all:2.4.11', 'org.spockframework:spock-core:1.1-groovy-2.4' ) } test { useJUnit { includeCategories 'com.testwithspring.master.UnitTest' } testLogging { showStandardStreams = true } }
This configuration adds a source set called integrationTest
to our build. This means that the src/integrationTest/groovy directory contains the source code of our our integration tests and the src/integrationTest/resources directory contains the resources of our integration tests.
Second, we have to change the name of the root directory from integrationTest to integration-test. We can do this by specifying the value of the dirName
configuration option.
After we have changed the name of the root directory, our build.gradle file looks as follows:
buildscript { repositories { jcenter() } dependencies { classpath( 'org.unbroken-dome.gradle-plugins:gradle-testsets-plugin:1.4.2' ) } } apply plugin: 'groovy' apply plugin: 'org.unbroken-dome.test-sets' testSets { integrationTest { dirName = 'integration-test' } } repositories { mavenCentral() } dependencies { compile( 'org.slf4j:slf4j-api:1.7.25', 'org.slf4j:slf4j-log4j12:1.7.25', 'log4j:log4j:1.2.17' ) testCompile( 'junit:junit:4.12', 'org.codehaus.groovy:groovy-all:2.4.11', 'org.spockframework:spock-core:1.1-groovy-2.4' ) } test { useJUnit { includeCategories 'com.testwithspring.master.UnitTest' } testLogging { showStandardStreams = true } }
Let’s move on and write one integration test.
Writing a Simple Integration Test
The GetMessageSpec
class contains one feature method which ensures that the getMessage()
method of the MessageService
class returns the correct message. Also, because we want to configure the category of this specification class, we have to annotate it with the @Category
annotation.
The source code of our specification class looks as follows:
import org.junit.experimental.categories.Category import spock.lang.Specification @Category(IntegrationTest.class) class GetMessageSpec extends Specification { def messageService = new MessageService() def 'Get message'() { expect: 'Should return the correct message' println 'Integration test: should return the correct message' messageService.getMessage() == 'Hello World!' } }
By the way, we print information to System.out
only because it’s the easiest way to see which feature methods are run. You shouldn’t do this in a real software project.
The IntegrationTest
interface is a marker interface that identifies our integration tests. Its source code looks as follows:
interface IntegrationTest { }
Next, we have to configure the task that runs our integration tests.
Configuring the Task That Runs Our Integration Test
When we added a test set called integrationTest
to our build, the Gradle TestSets plugin created a task called integrationTest
that runs our integration tests. However, because we need to fulfill the requirements of our Gradle build, we have to make some changes to its configuration. We can make these changes by following these steps:
First, we have to ensure that unit tests are run before integration tests and that integration tests are run when we invoke the build
task. We can do this by following these steps:
- Ensure that our integration tests are run before the
check
task and that thecheck
task fails the build if an integration test fails. - Ensure that our unit tests are run before our integration tests. This guarantees that our unit tests are run even if our integration tests fail.
After we have made these changes, our build.gradle file looks as follows:
buildscript { repositories { jcenter() } dependencies { classpath( 'org.unbroken-dome.gradle-plugins:gradle-testsets-plugin:1.4.2' ) } } apply plugin: 'groovy' apply plugin: 'org.unbroken-dome.test-sets' testSets { integrationTest { dirName = 'integration-test' } } check.dependsOn integrationTest integrationTest.mustRunAfter test repositories { mavenCentral() } dependencies { compile( 'org.slf4j:slf4j-api:1.7.25', 'org.slf4j:slf4j-log4j12:1.7.25', 'log4j:log4j:1.2.17' ) testCompile( 'junit:junit:4.12', 'org.codehaus.groovy:groovy-all:2.4.11', 'org.spockframework:spock-core:1.1-groovy-2.4' ) } test { useJUnit { includeCategories 'com.testwithspring.master.UnitTest' } testLogging { showStandardStreams = true } }
Second, we have to ensure that the HTML reports of unit and integration tests are written to separate report directories. If we don’t do this, the HTML reports of our unit and integration tests are created to the same report directory. In other words, if we run both unit and integration tests, we can see only the HTML report that contains the test results of our integration tests.
We can configure the report directory of a task whose type is Test
by setting the value of the reports.html.destination
configuration option. Because we want to use separate report directories, we have to append the name of invoked task to the reporting base directory.
After we have done this, our build.gradle file looks as follows:
buildscript { repositories { jcenter() } dependencies { classpath( 'org.unbroken-dome.gradle-plugins:gradle-testsets-plugin:1.4.2' ) } } apply plugin: 'groovy' apply plugin: 'org.unbroken-dome.test-sets' testSets { integrationTest { dirName = 'integration-test' } } check.dependsOn integrationTest integrationTest.mustRunAfter test tasks.withType(Test) { reports.html.destination = file("${reporting.baseDir}/${name}") } repositories { mavenCentral() } dependencies { compile( 'org.slf4j:slf4j-api:1.7.25', 'org.slf4j:slf4j-log4j12:1.7.25', 'log4j:log4j:1.2.17' ) testCompile( 'junit:junit:4.12', 'org.codehaus.groovy:groovy-all:2.4.11', 'org.spockframework:spock-core:1.1-groovy-2.4' ) } test { useJUnit { includeCategories 'com.testwithspring.master.UnitTest' } testLogging { showStandardStreams = true } }
Third, we have to configure the integrationTest
task by following these steps:
- Ensure that our integration tests are run by JUnit. Even though JUnit is enabled by default, we have to do this because we want to make changes to the JUnit configuration.
- Configure JUnit to run only integration tests that belong to the
IntegrationTest
category. We can do this by setting the fully qualified class name of theIntegrationTest
interface as the value of theincludeCategories
JUnit configuration option. - Ensure that Gradle prints the information that is written to
System.out
orSystem.err
when our integration tests are run. By the way, we have to do this only because our feature method writes information toSystem.out
.
After we have configured the integrationTest
task, our build.gradle file looks as follows:
buildscript { repositories { jcenter() } dependencies { classpath( 'org.unbroken-dome.gradle-plugins:gradle-testsets-plugin:1.4.2' ) } } apply plugin: 'groovy' apply plugin: 'org.unbroken-dome.test-sets' testSets { integrationTest { dirName = 'integration-test' } } check.dependsOn integrationTest integrationTest.mustRunAfter test tasks.withType(Test) { reports.html.destination = file("${reporting.baseDir}/${name}") } repositories { mavenCentral() } dependencies { compile( 'org.slf4j:slf4j-api:1.7.25', 'org.slf4j:slf4j-log4j12:1.7.25', 'log4j:log4j:1.2.17' ) testCompile( 'junit:junit:4.12', 'org.codehaus.groovy:groovy-all:2.4.11', 'org.spockframework:spock-core:1.1-groovy-2.4' ) } test { useJUnit { includeCategories 'com.testwithspring.master.UnitTest' } testLogging { showStandardStreams = true } } integrationTest { useJUnit { includeCategories 'com.testwithspring.master.IntegrationTest' } testLogging { showStandardStreams = true } }
Let’s move on and find out how we can run our unit and integration tests.
Running Unit and Integration Tests
We can run our tests by using one of the following options:
First, if we want to run only unit tests, we can use one of these two options:
- We can run unit tests by running the command: gradle clean test at command prompt.
- If we want to run our build and exclude integration tests, we have to run the command: gradle clean build -x integrationTest at command prompt.
When we run our unit tests, we should see an output that looks as follows:
$ gradle clean test > Task :test com.testwithspring.master.MessageServiceSpec > Get message STANDARD_OUT Unit test: should return the correct message BUILD SUCCESSFUL in 7s 7 actionable tasks: 7 executed
Second, if we want to run only integration tests, we can use one of these two options:
- We can run integration tests by running the command: gradle clean integrationTest at command prompt.
- We can run our build and exclude unit tests by running the command: gradle clean build -x test at command prompt.
When we run our integration tests, we should see an output that looks as follows:
$ gradle clean integrationTest > Task :integrationTest com.testwithspring.master.GetMessageSpec > Get message STANDARD_OUT Integration test: should return the correct message BUILD SUCCESSFUL in 2s 7 actionable tasks: 7 executed
Third, if we want to run all tests, we can use one of these two options:
- We can run unit and integration tests by running the command: gradle clean test integrationTest at command prompt.
- We can run our build by running the command: gradle clean build at command prompt.
When we run all tests, we should see an output that looks as follows:
$ gradle clean build > Task :test com.testwithspring.master.MessageServiceSpec > Get message STANDARD_OUT Unit test: should return the correct message > Task :integrationTest com.testwithspring.master.GetMessageSpec > Get message STANDARD_OUT Integration test: should return the correct message BUILD SUCCESSFUL in 3s 10 actionable tasks: 10 executed
Additional Reading:
Let’s summarize what we learned from this lesson.
Summary
This lesson has taught us three things:
- We can add custom test sets to our Gradle build by using the Gradle TestSets plugin.
- Because we use the Groovy plugin, the Gradle TestSets plugin allows us to compile and run integration tests which are written by using the Groovy programming language.
- When we add a new test set to our Gradle build, the Gradle TestSets plugin creates a task that runs the tests which belong to our custom test set.