Keeping your models clean with Lombok Part 1

Keeping your models clean with Lombok Part 1

When writing automation tests we often create model classes that are going to keep the properties of the class, and provide certain methods for accessing the properties. This is helpful to write clean code, and provide the necessary objects in another classes instead of creating methods with lots of input parameters. Lets see the following example:

We need to test an endpoint that is going to allow us to insert new address into the system. For this particular reason we would like to have a model class that is going to hold the properties of the address inside the Address class. By implementing this approach we can easily pass the Address object into the method of the client class that is responsible for sending the request.

public class Address {
    private String street;
    private String suite;
    private String city;
    private String zipCode;
    private Geo geo;
}

// Example of calling the method in the client class.
addressClinet.postAddress(new Address());

The code snippet addressClient.postAddress(new Address()); is going to send an empty Address object to our endpoint. In order to populate the properties of our Address class we would have to expose constructor that would allow us to initialize certain properties, or to expose Getters / Setters to set the values of certain properties. We can achieve this easily in Java by generating Constructor or Getters / Setters, which is supported by most IDEs. For the Address class this is not going to bring any headaches, because we don’t have a lot of properties. But in cases where we have 10, 15 or more properties in a class, the generated constructors and getter / setter methods are going to make the class really hard to read. Another thing that can be thrown is that the constructors, getters / setters could be easily generated, but the problem with this is that they would have to be re-generated again when new property is introduced into the class, or a property is being excluded from the class.
In such cases Lombok is really helpful, and can make the model class more readable. When using IntelliJ make sure you have the Lombok plugin installed, this is going to help with the productivity when writing the code.

Let’s start with the basic Lombok annotations that are commonly used in every model class that we are going to write.
For model classes that need constructors the following annotations could be used: @NoArgsConstructor, @AllArgsConstructor.
The annotation @NoArgsConstructor is going to generate constructor that does not require any parameters. The annotation @AllArgsConstructor is going to generate constructor that is going to have parameters with all the properties of the class.

// Example using Lombok annotation

@AllArgsConstructor
@NoArgsConstructor
public class Address {
    private String street;
    private String suite;
    private String city;
    private String zipCode;
    private Geo geo;
}

// Example not using Lombok annotation
public class Address {

    private String street;
    private String suite;
    private String city;
    private String zipCode;
    private Geo geo;

    public Address() {
        
    }
    
    public Address(String street, String suite, String city, String zipCode, Geo geo) {
        this.street = street;
        this.suite = suite;
        this.city = city;
        this.zipCode = zipCode;
        this.geo = geo;
    }
}

In the example above we can see the number of lines that we have saved by using the Lombok annotations. Lombok also allow us to generate setters / getter methods, which could be done with @Getter and @Setter annotations. With the following example we can compare the difference of the generated Setters / Getters and the Lombok annotations.

// Example using Lombok annotations

@Getter
@Setter
public class Address {
    private String street;
    private String suite;
    private String city;
    private String zipCode;
    private Geo geo;
}

// Example not using Lombok annotation

public class Address {
    private String street;
    private String suite;
    private String city;
    private String zipCode;
    private Geo geo;

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    public String getSuite() {
        return suite;
    }

    public void setSuite(String suite) {
        this.suite = suite;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getZipCode() {
        return zipCode;
    }

    public void setZipCode(String zipCode) {
        this.zipCode = zipCode;
    }

    public Geo getGeo() {
        return geo;
    }

    public void setGeo(Geo geo) {
        this.geo = geo;
    }
}

So far we have covered the commonly used Lombok annotations, but in some cases we would need to use the methods equals() and hashCode() that are going to allow us to compare two Address objects, and in some cases we would need to use the method toString() to print the state of the object. By using Lombok we can achieve this by using the annotations: @EqualsAndHashCode and @ToString. The example below is going to describe the usage of these annotations.

// Example using Lombok annotations

@EqualsAndHashCode
@ToString
public class Address {
    private String street;
    private String suite;
    private String city;
    private String zipCode;
    private Geo geo;
}

// Example not using Lombok annotations

public class Address {
    private String street;
    private String suite;
    private String city;
    private String zipCode;
    private Geo geo;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Address address = (Address) o;
        return Objects.equals(street, address.street) &&
                Objects.equals(suite, address.suite) &&
                Objects.equals(city, address.city) &&
                Objects.equals(zipCode, address.zipCode) &&
                Objects.equals(geo, address.geo);
    }

    @Override
    public int hashCode() {
        return Objects.hash(street, suite, city, zipCode, geo);
    }

    @Override
    public String toString() {
        return "Address{" +
                "street='" + street + '\'' +
                ", suite='" + suite + '\'' +
                ", city='" + city + '\'' +
                ", zipCode='" + zipCode + '\'' +
                ", geo=" + geo +
                '}';
    }
}

So far we have covered how to use the annotations @NoArgsConstructor, @AllArgsConstructor, @Getter, @Setter, @EqualsAndHashCode, @ToString. In cases when you know that you are going to cover: @Getter, @Setter, @EqualsAndHashCode, @ToString, you can easily replace these annotations with the @Data annotation.