When writing unit tests or integration tests, I have to create objects. Sometimes with a lot of mandatory fields.
Instead of repeating this, instead of using utils methods, I use the Fluent style.
With a few lines, I create all the needed objects for my tests.
Let’s see it in detail.
You may know the fluent style from Lombok.
Let’s start with a simple POJO (Plain Old Java Object).
class Car {
private final String brand;
private final String model;
private int year;
private int places;
private int fuelCapacity;
...
// constructors
...
// getters
...
// setters
...
}
As the amount of fields increases, the getters and setters also increase.
The mandatory fields have the final keyword and are present in all the constructors.
With Lombok, the POJO is clearly simplified.
@Builder
@Data
class Car {
private final String brand;
private final String model;
private int year;
private int places;
private int fuelCapacity;
}
The constructors, getters and setters are now handled by Lombok.
To create a Car entity I have those two options:
var car1 = new Car("Ford", "Mondeo");
var car2 = Car.builder().brand("Ford").model("Mondeo").build();
The second method is the so-called fluent style. I add the values per field as needed and generate the entity at the end with the method build().
Ok, that said, let’s go back to our case. The test data.
If I need to create a car (or many) for every single test, I will have a lot of repeated data.
To complexify a little bit more the example, I will also need a City entity (where the cars are being sold) and a User entity (owners of the cars).
So, for the major part of the unit tests, I need at least: one car, one city and one user. But sometimes, I need more cars, more cities and more users.
Let’s use a Dataset builder using the fluent style.
class DatasetBuilder {
private DatasetBuilder() {
// it's a utility class, avoid having a public constructor
}
public static InnerDatasetBuilder builder() {
return new InnerDatasetBuilder();
}
static class InnerDatasetBuilder {
private List<Car> cars;
private List<City> cities;
private List<User> users;
InnerDatasetBuilder() {
this.cars = new ArrayList<>();
this.cities = new ArrayList<>();
this.users = new ArrayList<>();
}
public InnerDatasetBuilder withCars(int amount) {
for (int i = 0; i < amount; i++) {
this.cars.add(Car.builder().brand("brand_" + i).model("model_" + i).build());
}
return this;
}
public InnerDatasetBuilder withCities(int amount) {
for (int i = 0; i < amount; i++) {
this.cities.add(City.builder().name("city_" + i).build());
}
return this;
}
public InnerDatasetBuilder withUsers(int amount) {
for (int i = 0; i < amount; i++) {
this.users.add(User.builder().firstName("first_" + i).lastName("last_" + i).build());
}
return this;
}
public void build() {
session.add(this.cars);
session.add(this.cities);
session.add(this.users);
session.commit();
}
}
}
Let’s analyse the previous code.
I have a single entry point, the static method DatasetBuilder.builder(). As the constructor of DatasetBuilder is private and the inner class is protected.
Once called builder(), I have access to the methods of the inner class InnerDatasetBuilder, withCars, withCities and withUsers.
Those methods accept the amount of items to create. The items will be created dynamically.
Finally, the method InnerDatasetBuilder.build() will save all the cars, cities and users in the database.
Here is how I use it:
DatasetBuilder.builder().
.withCars(15)
.withCities(5)
.withUsers(10)
.build(session);
Five lines and I fill my database with all the information needed per test.
I use this method to populate the database of my tests when I need many related entities.
Of course, I can add as many withXXX methods as needed.
If you want to learn more about good quality code, make sure to follow me on Youtube.



Leave a comment