Show Navigation

Grails Code Coverage

In this guide you'll learn how to improve your code coverage using Clover.

Authors: Sergio del Amo

Grails Version: 4.0.1

1 Grails Training

Grails Training - Developed and delivered by the folks who created and actively maintain the Grails framework!.

2 Getting Started

Atlassian Clover provides Java and Groovy developers a reliable source for code coverage analysis.

Since April 11, 2017, Clover is Open Source.

Grails 3 uses the Gradle Build System for build related tasks, such as compilation, running tests, and producing binary distributions of your project.

In this guide we are going to install a Gradle plugin to get code coverage of our Grails application.

2.1 What you will need

To complete this guide, you will need the following:

  • Some time on your hands

  • A decent text editor or IDE

  • JDK 1.8 or greater installed with JAVA_HOME configured appropriately

2.2 How to complete the guide

To get started do the following:

or

The Grails guides repositories contain two folders:

  • initial Initial project. Often a simple Grails app with some additional code to give you a head-start.

  • complete A completed example. It is the result of working through the steps presented by the guide and applying those changes to the initial folder.

To complete the guide, go to the initial folder

  • cd into grails-guides/grails-code-coverage/initial

and follow the instructions in the next sections.

You can go right to the completed example if you cd into grails-guides/grails-code-coverage/complete

3 Writing the Application

3.1 Gradle Clover Plugin

We are going to use the Gradle Clover Plugin to generating a code coverage report using Clover.

We are going to create a gradle file to keep the Clover configuration in one place

gradle/clover.gradle
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.bmuschko:gradle-clover-plugin:2.1.1'
    }
}

apply plugin: 'com.bmuschko.clover'

dependencies {
    clover 'org.openclover:clover:4.4.1'
}

clover {
    licenseLocation = File.createTempFile('clover', '.license').absolutePath (1)

    excludes = ['**/Application.groovy', (2)
                '**/BootStrap.groovy',
                '**/UrlMappings.groovy',
                '**/*GrailsPlugin.groovy',
                '**/*Mock.groovy',
    ]

    testIncludes = ['**/*Spec.groovy'] (3)
    report { (4)
        html = true
        xml = true
    }
}
1 Although, Clover is open source, you need to create a dummy license file.
2 We don’t want certain files to pollute our code coverage reports.
3 We want to include our Spock specifications as test files.
4 We want reports in both XML and HTML

We are going to reference this file from the ROOT build.gradle

build.gradle
buildscript {
    repositories {
        mavenLocal()
        maven { url "https://repo.grails.org/grails/core" }
    }
    dependencies {
        classpath "org.grails:grails-gradle-plugin:$grailsVersion"
        classpath "com.bertramlabs.plugins:asset-pipeline-gradle:3.0.11"
        classpath "org.grails.plugins:hibernate5:7.0.0"
        classpath 'com.bmuschko:gradle-clover-plugin:2.2.4' (1)
    }
}

version "0.1"
group "grails.code.coverage"

apply plugin:'groovy'
apply plugin:"eclipse"
apply plugin:"idea"
apply plugin:"war"
apply plugin:"org.grails.grails-web"
apply plugin:"org.grails.grails-gsp"
apply plugin:"com.bertramlabs.asset-pipeline"
apply from: "${project.projectDir}/gradle/clover.gradle"  (2)

repositories {
    maven { url "https://repo.grails.org/grails/core" }
}

configurations {
    developmentOnly
    runtimeClasspath {
        extendsFrom developmentOnly
    }
}
dependencies {
    developmentOnly("org.springframework.boot:spring-boot-devtools")
    compile "org.springframework.boot:spring-boot-starter-logging"
    compile "org.springframework.boot:spring-boot-autoconfigure"
    compile "org.grails:grails-core"
    compile "org.springframework.boot:spring-boot-starter-actuator"
    compile "org.springframework.boot:spring-boot-starter-tomcat"
    compile "org.grails:grails-web-boot"
    compile "org.grails:grails-logging"
    compile "org.grails:grails-plugin-rest"
    compile "org.grails:grails-plugin-databinding"
    compile "org.grails:grails-plugin-i18n"
    compile "org.grails:grails-plugin-services"
    compile "org.grails:grails-plugin-url-mappings"
    compile "org.grails:grails-plugin-interceptors"
    compile "org.grails.plugins:cache"
    compile "org.grails.plugins:async"
    compile "org.grails.plugins:scaffolding"
    compile "org.grails.plugins:events"
    compile "org.grails.plugins:hibernate5"
    compile "org.hibernate:hibernate-core:5.4.0.Final"
    compile "org.grails.plugins:gsp"
    compile "io.micronaut:micronaut-inject-groovy"

    console "org.grails:grails-console"
    profile "org.grails.profiles:web"
    runtime "org.glassfish.web:el-impl:2.1.2-b03"
    runtime "com.h2database:h2"
    runtime "org.apache.tomcat:tomcat-jdbc"
    runtime "com.bertramlabs.plugins:asset-pipeline-grails:3.0.11"
    testCompile "org.grails:grails-gorm-testing-support"
    testCompile "org.grails:grails-web-testing-support"
}

bootRun {
    jvmArgs(
            '-Dspring.output.ansi.enabled=always',
            '-noverify',
            '-XX:TieredStopAtLevel=1',
            '-Xmx1024m')
    sourceResources sourceSets.main
}


assets {
    minifyJs = true
    minifyCss = true
}
1 Add the plugin as a build script depedency
2 Apply the build file with Clover configuration.

3.2 Code and test

We have a domain class

grails-app/domain/demo/Person.groovy
package demo

class Person {
    String name
    boolean active
}

We have also a service which encapsulates the GORM queries against the previous domain class.

grails-app/services/demo/PersonGormService.groovy
package demo

import grails.gorm.services.Service
import grails.gorm.transactions.ReadOnly

@Service(Person)
interface PersonGormService {

    @ReadOnly
    List<Person> findAllByActive()
}

We have a Groovy POJO.

src/main/groovy/demo/Name.groovy
package demo

import groovy.transform.Canonical
import groovy.transform.CompileStatic

@Canonical
@CompileStatic
class Name {
    String firstName
    String lastName
}

The business logic of this app is in the NameService which guesses the firstName and lastName of each of the Person instances.

grails-app/services/demo/NameService.groovy
package demo

import groovy.transform.CompileStatic

@CompileStatic
class NameService {

    PersonGormService personGormService

    List<Name> findAllPersonNames() {
        personGormService.findAllByActive().collect { Person person ->
          nameWithFullName(person.name)
        }
    }

    Name nameWithFullName(String fullname) {
        if ( fullname.contains('del ') ) {
            return nameWithSeparator(fullname, 'del')
        }
        return nameWithSeparator(fullname, ' ')

    }

    Name nameWithSeparator(String fullname, String separator) {
        String firstName
        String lastName
        String[] arr = fullname.split(separator)
        if ( arr.length > 0 ) {
            firstName = "${arr[0]}${separator}".toString()
            int from = firstName.length()
            lastName = fullname.substring(from, fullname.length())
        } else {
            firstName = fullname
        }
        new Name(firstName: firstName?.trim(), lastName: lastName?.trim())
    }
}

We have written a test for this service:

src/test/groovy/demo/NameServiceSpec.groovy
package demo

import grails.testing.services.ServiceUnitTest
import spock.lang.Specification

class NameServiceSpec extends Specification implements ServiceUnitTest<NameService> {

    def "findAllPersonNames differentiates between names containing the substring 'del'"() {

        given:
        service.personGormService = Stub(PersonGormService) {
            findAllByActive() >> [
                    // new Person(name: 'Sergio del Amo', active: true), (1)
                    new Person(name: 'Graeme Rocher', active: true),]
        }

        when:
        List<Name> names = service.findAllPersonNames()

        then:
        names.each { Name name ->
            assert [
                    new Name(firstName: 'Sergio del', lastName: 'Amo'),
                    new Name(firstName: 'Graeme', lastName: 'Rocher')
            ].contains(name)
        }
    }

}
1 Uncomment this line to increase your code coverage.

4 Running the Application

To generate the code coverage report use the ./gradlew cloverGenerateReport command which will generate the reports under: build/reports/clover/html/index.html

Explore your report and improve the project coverage:

report

5 Do you need help with Grails?

Object Computing, Inc. (OCI) sponsored the creation of this Guide. A variety of consulting and support services are available.

OCI is Home to Grails

Meet the Team