54

I am writing unit tests for my project and am trying to achieve at least 80% code coverage. Problem is that I am using lombok's @Data annotation for generating getters and setters and when I run my unit tests, all those getters and setters along with other methods like toString, equals, hashcode etc are missed and my code coverage takes a hit. Is there any workaround for this. I have been searching a lot about this but haven't been able to find anything which could help out. Any help on this would be appreciated.

I am using Eclemma for code coverage analysis.

Varun Sharma
  • 1,602
  • 1
  • 13
  • 37
  • as Nico Van Belle said unit test classes as not written for code coverage...the main aim should be validation of units...later on if some issues are there , these classes should help them find it. @NicoVanBelle lombok is not that bad..:p:) – Akshay Jun 16 '17 at 08:36
  • 1
    @NicoVanBelle I understand your point. I was just mentioning that I am aiming for about 80% of code coverage but that is not the reason I am writing the test cases. The intention is to test different units independently. – Varun Sharma Jun 16 '17 at 08:40
  • @VarunSharma I think that means your class isn't getting covered. Did you change the MODEL_PACKAGE constant to refer to your package? – Akshay Jun 16 '17 at 08:46
  • @Akshay I didn't understand what you meant by MODEL_PACKAGE – Varun Sharma Jun 16 '17 at 09:33
  • Add a line private static final String MODEL_PACKAGE = "your package name"; – Akshay Jun 16 '17 at 09:34
  • @Akshay, do you mean add it in every file where I use the annotation? and what does this do, if you could please point out – Varun Sharma Jun 16 '17 at 09:37
  • In your unit test class just specify the package name...you must be getting red on the getters/setters class as it is not getting covered in the unit test class – Akshay Jun 16 '17 at 09:38
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/146850/discussion-between-varun-sharma-and-akshay). – Varun Sharma Jun 16 '17 at 09:44
  • See https://www.jdev.it/tips-unit-testing-javabeans/ – JavaTec Jan 19 '18 at 20:52
  • 1
    Solution: https://medium.com/@mladen.bolic/lombok-data-improve-your-code-coverage-a74fb624a72b – Rishabh Agarwal Feb 13 '19 at 16:26

5 Answers5

86

In 0.8.0 release, Jacoco added support for filtering out all methods annotated with @lombok.Generated from their reports. The only thing you need to change is to add lombok.config to the root of your project with the following settings:

config.stopBubbling = true
lombok.addLombokGeneratedAnnotation = true
  • config.stopBubbling = true tells Lombok that this is your root directory and that it should not search parent directories for more configuration files (you can have more than one lombok config files in different directories/packages).
  • lombok.addLombokGeneratedAnnotation = true will add @lombok.Generated annotation to all Lombok generated methods.

And that's it. Jacoco will filter Lombok auto-generated methods, and if you give your best, your code coverage could be close to 100% :))

mladzo
  • 1,885
  • 1
  • 18
  • 10
72

First of all, @Data annotation is the combination of @ToString, @EqualsAndHashCode, @Getter, @Setter.

If you just need Lombok to create getters and setters automatically, you can use only @Getter and @Setter annotations instead of @Data.

Besides, to keep the methods created by Lombok outside of this coverage, you can create a lombok.config file in your root directory and have these two lines:

config.stopBubbling = true
lombok.addLombokGeneratedAnnotation = true

After adding this line, when you go to Sonar, you will see that these classes are covered 100%.

ahmetcetin
  • 2,621
  • 1
  • 22
  • 38
12

When equals and hashcode are needed, they can be unit tested very thouroughly by using EqualsVerifier. EqualsVerifier is an opensource JUnit library that generates the unit tests for all parts of the equals and hashCode contracts, something that is not straight forward to achieve even when writing the tests by hand.

Example usage:

@Test
public void equalsContract() {
    EqualsVerifier.forClass( MyAwesomeLombokedDataClass.class )
        .suppress( Warning.STRICT_INHERITANCE )
        .verify();
}
Chris K
  • 11,622
  • 1
  • 36
  • 49
4

create a lombok.config file at the root level and add this code

config.stopBubbling = true
lombok.addLombokGeneratedAnnotation = true

would be ignored by code coverage

Abdu E.
  • 41
  • 2
1

You can try EqualsVerifier.
If you want to try manually, this could help a bit(My learning note). This covers 96% mutation testing as well:

@Getter
@Setter
@Builder
@ToString
@Component
@EqualsAndHashCode
@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)
public class ValidationEntity {
    private boolean valid;
    private int lineNumber;
    private int columnNumber;
    private String inputMessage;
    private String validationMessage;

    @JsonCreator
    ValidationEntity(@JsonProperty("valid") final boolean valid, @JsonProperty("lineNumber") final int lineNumber,
                     @JsonProperty("columnNumber") final int columnNumber, @JsonProperty("inputMessage") final String inputMessage,
                     @JsonProperty("validationMessage") final String validationMessage) {
        this.valid = valid;
        this.lineNumber = lineNumber;
        this.columnNumber = columnNumber;
        this.inputMessage = inputMessage;
        this.validationMessage = validationMessage;
    }

    public static ValidationEntityBuilder builder(String inputMessage) {
        return new ValidationEntityBuilder().inputMessage(inputMessage);
    }
}

Here is my test class:

/**
 * Validation Response Entity has the response details for all configured editor methods.
 * Tests are based on:
 * https://www.artima.com/lejava/articles/equality.html
 */
public class ValidationEntityTest {

    @Test
    public void newValidationEntityWithBuilder_whenEqual_ThenTrue() {
        ValidationEntity x = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();
        ValidationEntity y = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();
        ValidationEntity z = x;

        //reflexive: for any non-null value x, the expression x.equals(x) should return true.
        Assert.assertTrue(x.equals(x));
        //symmetric: for any non-null values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
        Assert.assertTrue(x.equals(y) && y.equals(x));
        //transitive: for any non-null values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
        Assert.assertTrue(x.equals(z) && y.equals(z) && x.equals(y));
        //consistent: for any non-null values x and y, multiple invocations of x.equals(y) should consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
        Assert.assertTrue(x.equals(y) && x.equals(y) && x.equals(y) && x.equals(y));
        //Null check: for any non-null value x, x.equals(null) should return false.
        Assert.assertFalse(x.equals(null));

        Assert.assertEquals(x, y);
        Assert.assertEquals(x.hashCode(), y.hashCode());
        Assert.assertEquals(x.toString(), y.toString());
        Assert.assertEquals(x.getInputMessage(), y.getInputMessage());
        Assert.assertEquals(x.getValidationMessage(), y.getValidationMessage());
        Assert.assertEquals(x.getLineNumber(), y.getLineNumber());
        Assert.assertEquals(x.getColumnNumber(), y.getColumnNumber());
        Assert.assertEquals(x.isValid(), y.isValid());
    }

    @Test
    public void newValidationEntityWithBuilder_whenNotEqual_ThenFalse() {
        ValidationEntity x = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();
        ValidationEntity y = ValidationEntity.builder("Custom Input Message").lineNumber(1).columnNumber(2).valid(false).validationMessage("Custom Validation Message").build();

        Assert.assertFalse(x.equals(y) && y.equals(x));
        Assert.assertNotEquals(x.hashCode(), y.hashCode());
        Assert.assertNotEquals(x.toString(), y.toString());
        Assert.assertNotEquals(x.getInputMessage(), y.getInputMessage());
        Assert.assertNotEquals(x.getValidationMessage(), y.getValidationMessage());
        Assert.assertNotEquals(x.getLineNumber(), y.getLineNumber());
        Assert.assertNotEquals(x.getColumnNumber(), y.getColumnNumber());
        Assert.assertNotEquals(x.isValid(), y.isValid());
    }

    @Test
    public void newValidationEntityWithBuilder_whenBothAreEqualAndObjectInstances_ThenTrue() {
        Object x = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();
        Object y = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();

        Assert.assertTrue(x.equals(y) && y.equals(x));
        Assert.assertEquals(x.hashCode(), y.hashCode());
        Assert.assertEquals(x.toString(), y.toString());
    }

    @Test
    public void newValidationEntityStringWithBuilder_whenSameInstanceToStringsAreCompared_ThenTrue() {
        String x = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").toString();
        String y = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").toString();

        Assert.assertEquals("ValidationEntity.ValidationEntityBuilder(valid=true, lineNumber=11, columnNumber=22, inputMessage=inputMessage, validationMessage=Validation Successful)", x);
        Assert.assertEquals("ValidationEntity.ValidationEntityBuilder(valid=true, lineNumber=11, columnNumber=22, inputMessage=inputMessage, validationMessage=Validation Successful)", y);
    }

    @Test
    public void newValidationEntityWithBuilder_whenOneIsInstanceAndOtherString_ThenFalse() {
        ValidationEntity x = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();
        String y = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build().toString();

        Assert.assertFalse(x.equals(y));
        Assert.assertFalse(y.equals(x));
        Assert.assertNotEquals(x.hashCode(), y.hashCode());
        Assert.assertEquals(x.toString(), y.toString());
    }

    @Test
    public void newValidationEntityWithBuilder_whenOneIsInstanceAndOtherOneIsAChild_ThenFalse() {
        ValidationEntity x = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();

        class ValidationEntityExtended extends ValidationEntity {
            String additionalDetail;

            public String getAdditionalDetail() {
                return this.additionalDetail;
            }

            public void setAdditionalDetail(final String additionalDetail) {
                this.additionalDetail = additionalDetail;
            }

            ValidationEntityExtended(@JsonProperty("valid") final boolean valid, @JsonProperty("lineNumber") final int lineNumber,
                                     @JsonProperty("columnNumber") final int columnNumber, @JsonProperty("inputMessage") final String inputMessage,
                                     @JsonProperty("validationMessage") final String validationMessage, String additionalDetail) {
                super(true, 11, 22, "inputMessage", "Validation Successful");
                this.additionalDetail = additionalDetail;
            }

            @Override
            public boolean equals(Object other) {
                boolean result = false;
                if (other instanceof ValidationEntityExtended) {
                    ValidationEntityExtended that = (ValidationEntityExtended) other;
                    result = (that.canEqual(this) && this.additionalDetail.equals(that.additionalDetail) && super.equals(that));
                }
                return result;
            }

            @Override
            public int hashCode() {
                return (41 * super.hashCode() + additionalDetail.hashCode());
            }

            @Override
            public boolean canEqual(Object other) {
                return (other instanceof ValidationEntityExtended);
            }

            @Override
            public String toString() {
                return "ValidationEntityExtended(valid=" + this.isValid() + ", lineNumber=" + this.getLineNumber() + ", columnNumber=" + this.getColumnNumber() + ", inputMessage=" + this.getInputMessage() + ", validationMessage=" + this.getValidationMessage() + ", additionalDetail=" + this.getAdditionalDetail() + ")";
            }
        }
        ValidationEntityExtended validationEntityExtended = new ValidationEntityExtended(true, 11, 22, "inputMessage", "Validation Successful", "additionalDetail");
        Assert.assertFalse(x.equals(validationEntityExtended));
        Assert.assertFalse(validationEntityExtended.equals(x));
        Assert.assertNotEquals(x.hashCode(), validationEntityExtended.hashCode());
        Assert.assertNotEquals(x.toString(), validationEntityExtended.toString());
    }

    @Test
    public void newValidationEntityWithBuilder_whenEachMethodIsEqual_ThenTrue() {
        ValidationEntity x = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();
        ValidationEntity y = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();

        Assert.assertTrue(x.equals(y) && y.equals(x));
        Assert.assertEquals(x.hashCode(), y.hashCode());
        Assert.assertEquals(x.toString(), y.toString());
        Assert.assertEquals(x.getInputMessage(), y.getInputMessage());
        Assert.assertEquals(x.getValidationMessage(), y.getValidationMessage());
        Assert.assertEquals(x.getLineNumber(), y.getLineNumber());
        Assert.assertEquals(x.getColumnNumber(), y.getColumnNumber());
        Assert.assertEquals(x.isValid(), y.isValid());
    }

    @Test
    public void newValidationEntityWithBuilder_whenInputMessageNullForOne_ThenFalse() {
        ValidationEntity x = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();
        ValidationEntity y = ValidationEntity.builder(null).lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();

        Assert.assertNotEquals(x, y);
        Assert.assertNotEquals(y, x);
        Assert.assertNotEquals(x.hashCode(), y.hashCode());
        Assert.assertNotEquals(x.toString(), y.toString());
    }

    @Test
    public void newValidationEntityWithBuilder_whenInputMessageNullForBoth_ThenTrue() {
        ValidationEntity x = ValidationEntity.builder(null).lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();
        ValidationEntity y = ValidationEntity.builder(null).lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();

        Assert.assertEquals(x, y);
        Assert.assertEquals(y, x);
        Assert.assertEquals(x.hashCode(), y.hashCode());
        Assert.assertEquals(x.toString(), y.toString());
    }

    @Test
    public void newValidationEntityWithBuilder_whenInputMessageNotEqual_ThenFalse() {
        ValidationEntity x = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();
        ValidationEntity y = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Custom Input Message").build();

        Assert.assertFalse(x.equals(y) && y.equals(x));
        Assert.assertNotEquals(x.hashCode(), y.hashCode());
        Assert.assertNotEquals(x.toString(), y.toString());
    }

    @Test
    public void newValidationEntityWithBuilder_whenValidNotEqual_ThenFalse() {
        ValidationEntity x = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();
        ValidationEntity y = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();
        y.setValid(false);

        Assert.assertFalse(x.equals(y) && y.equals(x));
        Assert.assertNotEquals(x.hashCode(), y.hashCode());
        Assert.assertNotEquals(x.toString(), y.toString());
    }

    @Test
    public void newValidationEntityWithBuilder_whenLineNumberNotEqual_ThenFalse() {
        ValidationEntity x = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();
        ValidationEntity y = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();
        y.setLineNumber(1);

        Assert.assertFalse(x.equals(y) && y.equals(x));
        Assert.assertNotEquals(x.hashCode(), y.hashCode());
        Assert.assertNotEquals(x.toString(), y.toString());
    }

    @Test
    public void newValidationEntityWithBuilder_whenColumnNumberNotEqual_ThenFalse() {
        ValidationEntity x = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();
        ValidationEntity y = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();
        y.setColumnNumber(2);

        Assert.assertFalse(x.equals(y) && y.equals(x));
        Assert.assertNotEquals(x.hashCode(), y.hashCode());
        Assert.assertNotEquals(x.toString(), y.toString());
    }

    @Test
    public void newValidationEntityWithBuilder_whenValidationMessageNullForOne_ThenFalse() {
        ValidationEntity x = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();
        ValidationEntity y = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage(null).build();

        Assert.assertNotEquals(x, y);
        Assert.assertNotEquals(y, x);
        Assert.assertNotEquals(x.hashCode(), y.hashCode());
        Assert.assertNotEquals(x.toString(), y.toString());
    }

    @Test
    public void newValidationEntityWithBuilder_whenValidationMessageBothNull_ThenTrue() {
        ValidationEntity x = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage(null).build();
        ValidationEntity y = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage(null).build();

        Assert.assertEquals(x, y);
        Assert.assertEquals(y, x);
        Assert.assertEquals(x.hashCode(), y.hashCode());
        Assert.assertEquals(x.toString(), y.toString());
    }

    @Test
    public void newValidationEntityWithBuilder_whenValidationMessageNotEqual_ThenFalse() {
        ValidationEntity x = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Validation Successful").build();
        ValidationEntity y = ValidationEntity.builder("inputMessage").lineNumber(11).columnNumber(22).valid(true).validationMessage("Custom Validation Message").build();

        Assert.assertFalse(x.equals(y) && y.equals(x));
        Assert.assertNotEquals(x.hashCode(), y.hashCode());
        Assert.assertNotEquals(x.toString(), y.toString());
    }

    @Test
    public void newValidationEntityWithAllArgsConstructor_whenEqual_ThenTrue() {
        ValidationEntity x = new ValidationEntity(true, 11, 22, "inputMessage", "Validation Successful");
        ValidationEntity y = new ValidationEntity(true, 11, 22, "inputMessage", "Validation Successful");

        Assert.assertTrue(x.equals(y) && y.equals(x));
        Assert.assertEquals(x.hashCode(), y.hashCode());
        Assert.assertEquals(x.toString(), y.toString());
        Assert.assertEquals(x.getInputMessage(), y.getInputMessage());
        Assert.assertEquals(x.getValidationMessage(), y.getValidationMessage());
        Assert.assertEquals(x.getLineNumber(), y.getLineNumber());
        Assert.assertEquals(x.getColumnNumber(), y.getColumnNumber());
        Assert.assertEquals(x.isValid(), y.isValid());
    }


    @Test
    public void newValidationEntityWithAllArgsConstructor_whenNotEqual_ThenFalse() {
        ValidationEntity x = new ValidationEntity(true, 11, 22, "inputMessage", "Validation Successful");
        ValidationEntity y = new ValidationEntity(false, 1, 1, "Custom inputMessage", "Custom Validation Successful");

        Assert.assertFalse(x.equals(y) && y.equals(x));
        Assert.assertNotEquals(x.hashCode(), y.hashCode());
        Assert.assertNotEquals(x.toString(), y.toString());
        Assert.assertNotEquals(x.getInputMessage(), y.getInputMessage());
        Assert.assertNotEquals(x.getValidationMessage(), y.getValidationMessage());
        Assert.assertNotEquals(x.getLineNumber(), y.getLineNumber());
        Assert.assertNotEquals(x.getColumnNumber(), y.getColumnNumber());
        Assert.assertNotEquals(x.isValid(), y.isValid());
    }
}
Anand Varkey Philips
  • 1,811
  • 26
  • 41
  • Crazy number of annotations.. somtimes i wonder , in the name of readability , they have added complexity... whole purpose is defeated by lambok – Stunner Dec 14 '20 at 03:49
  • @Stunner that stack is summarized in a meta annotation `@Data` which simplifies things considerably. It also keeps the source as minimal as possible, rather than potentially hundreds of getters and setters in the way of business logic. – Stephan Feb 11 '21 at 14:16