Rainer Hahnekamp

Ignoring Lombok Code in Jacoco

Update 15. January 2018: Jacoco 0.8.0 has been released. No need to build it from the SNAPSHOT version anymore.

Introduction

Test Coverage is a code metric that indicates how many lines of code, as a percent of the total, your tests execute. It can’t tell you anything about the quality of your tests, but it nevertheless is one of the most important metrics in use. Jacoco is one of the most prominent test coverage tools for Java.

Lombok is a Java library that generates common boilerplate code like getter/setter methods, hashCode, and builder classes during the compilation phase. This improves development speed significantly.

The Problem

Lombok causes problems when your project requires a minimum test coverage rate that is also checked by a CI System such as Jenkins or Travis. Jacoco can’t distinguish between Lombok’s generated code and the normal source code. As a result, the reported coverage rate drops unrealistically low. You the developer are left with two options:

  1. Write unit tests for generated code or
  2. Decrease the required coverage rate.

Neither option makes sense and neither is desirable.

The Solution

Luckily, beginning with version 0.8.0, Jacoco can detect, identify, and ignore Lombok-generated code. The only thing you as the developer have to do is to create a file named lombok.config in your directory’s root and set the following flag:

lombok.addLombokGeneratedAnnotation = true

This adds the annotation lombok.@Generated to the relevant methods, classes and fields. Jacoco is aware of this annotation and will ignore that annotated code.

Please keep in mind that you require at least version 0.8.0 of Jacoco and v1.16.14 of Lombok.

Showcase

Let’s suppose we have a Person class that contains fields for first- and lastname. We are using @Data which will generate the getter/setters, hashCode, toString and equals methods. We also use @Builder which generates - as the name says - a builder pattern for instantiating an object.

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class Person {
  private String firstname;
  private String lastname;
}

Then we have a PersonPrinter class that contains logic for printing a Person. We annotate it with @Log to instantiate a static logger and again @Data:

import lombok.Data;
import lombok.extern.java.Log;

@Log
@Data
public class PersonPrinter {
  private Person person;
  private String separator = " ";
  private String noLastnameLog = "That person has no name";

  public PersonPrinter(Person person) {
    this.person = person;
  }
  public String toString() {
    if ("".equals(person.getLastname())) {
      log.info(noLastnameLog);
      return "";
    }

    return String.format(person.getFirstname() + this.separator + 
      person.getLastname());
  }
}

The only logic which should be tested is PersonPrinter. The following two test cases should actually give us 100% test coverage:

public class PersonPrinterTest {
  @Test
  public void testDefault() {
    Person harrison = Person.builder()
      .firstname("John").lastname("Harrison").build();

    assertEquals("John Harrison",
      new PersonPrinter(harrison).toString());
  }

  @Test
  public void testNoLastname() {
    Person anonymous = Person.builder()
      .firstname("anonymous").lastname("").build();

    assertEquals("", new PersonPrinter(anonymous).toString());
  }
}

Unfortunately, this is not the case since Jacoco also counts the generated code from Lombok:

Jacoco Code Coverage Report including Lombok

By adding the flag lombok.addLombokGeneratedAnnotation = true before cleaning and running the tests again, we see that Jacoco has completely ignored the class Person and shows us 100% test coverage:

Jacoco Code Coverage Report excluding Lombok

As always the source code is available on GitHub.  It contains support for both Maven and Gradle.

Further Reading