This example demonstrates how we can implement our tests using groovy in a java based project. You might be asking why. Well, implementing tests with groovy has some advantages like easier mock creation, expressive business readable test method names, seamless adaptation with BDD frameworks, and so on. If you want to investigate testing java projects with groovy, the article will, then, provide you a starting point for your investigation.
Source code for the article: https://github.com/entrofi/spring/tree/master/groovy-intengration-tests
A few advantages which we can have if we write our tests with groovy
Even though I will not discuss the advantages, there is no harm in listing a few advantages testing java projects with groovy :
- The creation of mocks and stubs is easier in groovy. Using language features like closures and simple maps you can avoid the implementation of inner classes and express your mock objects using closures. Further information regarding this topic can be found in the official documentation.
- Expressive, human-readable test method name declarations,
- String interpolation,
- Easier to adapt with BDD frameworks in an expressive manner,
- Groovy provides many syntactic sugars for data creation,
- Test outputs can be more expressive, especially by using frameworks like Spock.
Let’s create our example setup now!
Setting up the project – Adding groovy support to unit tests
Although this example is not necessarily related with spring boot, we are going to use spring boot here to get up and running quickly. Go to spring initialzr page and create a Gradle backed spring boot java project with the following dependency spring-boot-starter-web
.
Our initial build.gradle
script will be similar to the following snippet:
plugins {
id 'org.springframework.boot' version '2.1.3.RELEASE'
id 'java'
}
apply plugin: 'io.spring.dependency-management'
group = 'net.entrofi.testing'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
Simple isn’t it? Our first job is to add groovy support to our unit tests. Afterward, we are going to extend this support to our spring boot integration tests.
Add Groovy support to the unit tests
Steps for adding groovy support to unit tests are as follows:
- Add groovy-all as a dependency with
testImplementation
scope to our build script.1dependencies { .... testImplementation'org.codehaus.groovy:groovy-all:2.4.15'
}
- Inform Gradle that we are going to put our groovy based test implementations under the source folder
src/test/groovy
.
1plugins { id 'org.springframework.boot'
version '2.1.3.RELEASE'
id 'java'
} apply plugin: 'io.spring.dependency-management'
group = 'net.entrofi.testing'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
- Add a line to apply the groovy plugin so that Gradle is able to process groovy files.
1apply plugin: 'groovy'
As soon as these steps are applied, our build script will be like the following snippet:
plugins {
id'org.springframework.boot' version'2.1.3.RELEASE'
id'java'
}
apply plugin:'io.spring.dependency-management'
apply plugin:'groovy'
group ='net.entrofi.testing'
version ='0.0.1-SNAPSHOT'
sourceCompatibility ='1.8'
repositories {
mavenCentral()
}
sourceSets {
test {
groovy {
srcDir file( 'src/test/groovy' )
}
}
}
dependencies {
implementation'org.springframework.boot:spring-boot-starter-web'
testImplementation'org.springframework.boot:spring-boot-starter-test'
testImplementation'org.codehaus.groovy:groovy-all:2.4.15'
}
Add a utility class to test with Groovy
We can now add some trivial utility class and create a unit test for it using groovy. Create a class called MathUtil
under the package net.entrofi.testing.groovyintengrationtests.util
, and implement a method that sums it’s arguments and returns the result.
public final class MathUtil {
public static int sum (int a, int b) {
return a + b;
}
}
Now create a groovy unit test file (MathUtilTest.groovy
) for this trivial utility class under src/test/groovy
folder.
package net.entrofi.testing.groovyintengrationtests.util
import org.junit.Test
import static org.junit.Assert.assertEquals
class MathUtilTest {
@Test
void "sum of 2 and 3 should return 5" () {
int result = 5 ;
assertEquals( 5 , MathUtil.sum( 2 , 3 ))
}
}
Run the test, and if everything goes fine, you can move on to the integration test configuration.
Setting up groovy backed springboot integration tests
This section is also similar to the previous one, with only slight differences. We will inform Gradle that our groovy based integration tests reside under src/integration/groovy
folder and extend our previous test configuration in order to reuse similar libraries that we will need for integration tests also.
In addition, we are going to configure the flow that our integration tests will be run in.
Configure groovy test sourceSets
Add the following lines to sourceSets
section of the build.gradle
file:
sourceSets {
test {
groovy {
srcDir file( 'src/test/groovy' )
}
}
integration {
groovy {
compileClasspath += main .output + test .output
runtimeClasspath += main .output + test .output
srcDir file( 'src/integration/groovy' )
}
java .srcDir project.file( 'src/integration/java' )
resources .srcDir project.file( 'src/integration/resources' )
}
}
“sourceSets
” in Gradle are instances of NamedDomainObjectContainer which means named extensions of dependency configurations will be supported by the relevant plugins. For instance, Gradle defines “implementation” (previously compile), “runtime“, “testImplementation“, etc., as predefined dependency scopes (configurations) by default. These configurations are further extended by plugins like the java plugin (or new ones are provided). That is to say, we are going to have independent dependency scopes for our new named configuration integration
, like integrationImplementation
, integrationRuntime
, and so on.
Configure integration-test related dependency scopes
We can now configure our integration
related scopes as extensions to test
configurations so that we can use similar dependencies from the test scope
configurations {
integrationRuntime .extendsFrom testRuntime
integrationImplementation .extendsFrom testImplementation
}
Define tasks to run integration tests
The next item in our build script configuration is defining the task that runs our integration tests and introducing this task to our build lifecycle. The task descriptions are as follows:
task ( 'integrationTest' , type: Test, description:'Runs the integration tests.' , group:'Verification' ) {
testClassesDirs = project .sourceSets .integration .output .classesDirs
classpath = project .sourceSets .integration .runtimeClasspath
}
In this task implementation, we named our task as integrationTest
, and marked it as a test task using the “type: Test
” parameter declaration. After defining the name and the type, we have informed Gradle that our integration test classes reside in the output directory of our integration
source set and also added the classpath from that source set to our task.
Introduce “integrationTest” task to build cycle
It’s now time to introduce this task to our build lifecycle. We would like to introduce this task in the check stage where we also run unit tests.
As you know integration tests are long-running tests, and we prefer to shorten our feedback lifecycle. If there is a failing test within our unit test scope, it’s better to stop the build-cycle before running integration tests. Therefore, we are going to make sure that our integration tests will run after unit tests:
check .dependsOn integrationTest integrationTest .mustRunAfter test
Implement a rest-endpoint and it’s integration test
Now, we’re done with the build script configuration. Let’s implement a trivial integration test in our project. In order to do this we will add a HelloWorldController
class to our project:
@RestController
public class HelloWorldController {
@GetMapping( "/hello" )
public String sayHello(@RequestParam(name = "name" ) String name) {
return "Hello " + name;
}
}
The next step is to add the corresponding spring boot integration test configuration and implementation under src/integration/groovy
:
@RunWith (SpringRunner.class )
@SpringBootTest (classes = [ GroovyIntengrationTestsApplication.class ], webEnvironment = RANDOM_PORT)
@AutoConfigureMockMvc
class HelloWorldControllerTest {
@Value ( ' ${local.server.port} ' )
private int port;
@Value ( ' ${deployment.environment.host:http://localhost} ' )
private String host;
@Autowired
private MockMvc mvc
@Test
void "given name is hasan hello should return 'hello hasan'" () {
mvc.perform(
get (getUrl( "/hello" ))
.param( 'name' ,'hasan' )
).andExpect(status().isOk())
.andExpect(content().string( 'Hello hasan' ));
}
private String getUrl( String uri) {
String url = host +":" + port;
url = StringUtils.isEmpty(uri) ? url : url + uri;
return url;
}
}
After implementing our test with groovy, we can run our integration tests using the following command:
$ ./gradlew integrationTest