Another Data POJO Builder for Tests
Learn how to use Builder4Test, a data POJO build for automated Java tests.
Join the DZone community and get the full member experience.
Join For FreeIf you feel that your tests look like a big Jenga tower you might find this article interesting. I know what you are thinking, how is another builder library going to help me? Moreover, some people might say it is a total overkill or there is no need for it (and they might be right) because every project is different, but there is something that all projects should have: good code quality. Not only for the production code but for the tests. In my experience, I have come to the conclusion that this is our weakest point, not because we are not willing to write good tests but because modifying and maintaining existing tests has been a difficult part of our task, especially when we are trying to revert this by re-writing it. When you read a test you can notice a few clues that lead to an inevitable refactor of it, for instance:
- When you need to scroll several times to finish reading one scenario.
- Where you don’t need to scroll so much but it has a lot of private methods, most of them doing very similar things and the only difference are a few parameters.
- Where you use production builders code to construct your data POJO but it is still difficult to understand and even to modify one simple value derives in a copy and paste/adding another private method with different set of parameters to build the POJO.
- And the complexity could go higher if you are building a complex tree of objects.
- When you have to mutate your object after it has been constructed.
Most of our tests could be simplified a lot by using data test builders. I have tried a few libraries in the past but none of them seem to resolve the majority of the problems. The library that we are going to explore here might fulfil your expectations and scenarios that you have in mind. Feel free to raise issues on github and PR’s are always welcome!
So let’s see some code! Right below we have a complex POJO that we want to build:
public class Order {
private List<Item> items;
private Customer customer;
private Vendor vendor;
private class Item {
private UUID id;
private Product product;
private int quantity;
private double price;
}
private class Product {
private String name;
private String description;
}
private class Customer {
String firstName;
String lastNAme;
Address address;
}
private class Vendor {
String name;
Address address;
}
private class Address {
String street;
String number;
String postCode;
}
}
Now for simplicity let’s assume that we have the traditional builders for each entity. In order to use these builders in your test you might have to create a class that contains a method similar to the one below:
public static Order buildDefaultOrder() {
return Order.builder()
.withItems(Item.builder()
.withId(UUID.randomUUID())
.withProduct(Product.builder()
.withName("test")
.withDescription("value")
.build())
.withQuantity(3)
.withPrice(2.2d)
.build())
.withCustomer(Customer.builder()
.withFirstName("Alan")
.withLastName("Joe")
.withAddress(Address.builder().build())
.build())
.withVendor(Vendor.builder().build())
.build();
}
This doesn’t look that bad, doesn’t it? It is easy to read, but what if I need to override some of those default values? Someone could argue that it can be achieved by mutating the object like this:
public void buildOrder(String firstName) {
Order order = buildDefaultOrder();
order.getCustomer().setFirstName(firstName);
}
and now imagine the code if I have to update only the LastName of the customer or both at the same time. By following the same line of thinking I would have something like this:
public void buildOrderWithLastName(String lastName) {
Order order = buildDefaultOrder();
order.getCustomer().setLastName(lastName);
}
public void buildOrderWithFirstNameAndLastName(String firstName, String lastName) {
Order order = buildDefaultOrder();
order.getCustomer().setFistName(firstName);
order.getCustomer().setLastName(lastName);
}
As you might have realised by now, the solution scales up in that way by adding methods per combination of data and compromising the immutability of our domain objects for the sake of our tests, which is not good at all.
Solution
Use Builder4Test! The library provides an intuitive DSL that also scales up in a very nice and simple way. You won’t have to add methods and no need to sacrifice the immutability of your domain objects.
How does it look? Let’s dig a little bit more and see how it works.
public static Field<String> firstName = new Field<>();
public static Field<String> lastName = new Field<>();
public static Creator<Order> orderCreator = (lookUp) ->
Order.builder()
.withItems(Item.builder()
.withId(UUID.randomUUID())
.withProduct(Product.builder()
.withName("test")
.withDescription("value")
.build())
.withQuantity(3)
.withPrice(2.2d)
.build())
.withCustomer(Customer.builder()
.withFirstName(lookUp.get(firstName, "Alan")
.withLastName(lookUp.get(lastName, "Joe")
.withAddress(Address.builder().build())
.build())
.withVendor(Vendor.builder().build())
.build()
}
Builder4Test DSL will require you to implement a Creator Interface, to define how to build the object and which fields you want to override, as well as what will be the default values for those in case you don’t want to override it.
Once you have your creator, you will consume it by Builder4Test DSL like this.
@Test
public void testbuildOrderWithFirstName() {
Builder.build()
.entity(orderCreator)
.override(firstName, "Thomas")
.get();
}
@Test
public void testbuildOrderWithLastName() {
Builder.build()
.entity(orderCreator)
.override(lastName, "Eddison")
.get();
}
@Test
public void testbuildOrderWithFirstNameAndLastName() {
Builder.build()
.entity(orderCreator)
.override(lastName, "Eddison")
.override(firstName, "Thomas")
.get();
}
Conclusion
One of the major benefits that you can get from Builder4Test:
- The tests will become more maintainable and flexible for eventual new requirements without affecting other tests,
- No need to add methods for each combination of data that you need to modify.
- Keep your objects immutable!
Builder4Test offers many other cool features like random generation of data. Please check the demo project to find more details about how to use the library.
Feel free to make contributions to improve it by checking Builder4Test Github project.
Opinions expressed by DZone contributors are their own.
Comments