How to use the Value Object pattern in the project.
In the previous article, we discussed Page Object pattern from Structural Patterns in Test Automation, which is very valuable for building robust and scalable test frameworks and tools. In this article, we’ll talk about Data Patterns and the value object pattern.
Data Patterns the main goal is to split data and test logic as well as to reduce boilerplate and code duplication in our tests. It should make them more understandable and easier in maintenance for anyone who works with them.
Value Object.
This pattern very simple, I think you all used it, but to my great regret, I have seen many projects where this is neglected, and in the end, it turns out to be very difficult. That’s really sad because from a design perspective Value Object can make your code more readable and significantly reduce the number of repeatable constructions. Why do we need to use the pattern of the Value Object? It is Immutable: once it is created, it cannot be changed, because this is its task, it serves to transfer data from point A to point B, and not to be modifiable or carry third-party effects.
/**
* Create user.
*
* @param firstName the first name.
* @param lastName the last name.
* @param age the age.
* @param isMarried the is married.
* @param accomplishments the accomplishments.
*/
public void createUser(String firstName, String lastName,
int age, boolean isMarried, List<String> accomplishments) {
enter(firstName, into("name"));
enter(lastName, into("lastName"));
enter(age, into("age"));
enterMaritalStatus(isMarried);
accomplishments.forEach(this::addAccomplishment);
}
We will convert this code:
/**
* The class User.
*/
public class User {
/**
* The First name.
*/
private String firstName;
/**
* The Last name.
*/
private String lastName;
/**
* The Age.
*/
private String age;
/**
* The Is married.
*/
private boolean isMarried;
/**
* The Accomplishments.
*/
private List<String> accomplishments;
/**
* Instantiates a new User.
*
* @param firstName the first name
* @param lastName the last name
* @param age the age
* @param isMarried the is married
* @param accomplishments the accomplishments
*/
public User(String firstName, String lastName, String age, boolean isMarried, List<String> accomplishments) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.isMarried = isMarried;
this.accomplishments = accomplishments;
}
/**
* Gets first name.
*
* @return the first name
*/
public String getFirstName() {
return firstName;
}
/**
* Gets last name.
*
* @return the last name
*/
public String getLastName() {
return lastName;
}
/**
* Gets age.
*
* @return the age
*/
public String getAge() {
return age;
}
/**
* Is married boolean.
*
* @return the boolean
*/
public boolean isMarried() {
return isMarried;
}
/**
* Gets accomplishments.
*
* @return the accomplishments
*/
public List<String> getAccomplishments() {
return accomplishments;
}
}
And use:
/**
* Create user.
*
* @param user this is object User user.
*/
public void createUser(User user) {
enter(user.firstName, into("name"));
enter(user.lastName, into("lastName"));
enter(user.age, into("age"));
enterMaritalStatus(user.isMarried);
user.accomplishments.forEach(this::addAccomplishment);
}
I’d explain the pattern this way. If we have multiple objects which have some common logic (in the case above createUser accepts five parameters — first name, last name, age, marital status, and accomplishments), it’s better to merge them into one entity. In this case, User will be our Value Object, which aggregates all needed information about the actual users into it.
To ease the process of creation of such objects you could use additional libraries, like Lombok in case you’re working with Java, this tool will create needed constructors, generate getters for all fields, and finalize them afterward without any manual work from your side.
Lombok converts annotations in source code to Java statements before the compiler processes them:
/**
* The class User.
*/
@Getter
public class User {
/**
* The First name.
*/
private String firstName;
/**
* The Last name.
*/
private String lastName;
/**
* The Age.
*/
private String age;
/**
* The Is married.
*/
private boolean isMarried;
/**
* The Accomplishments.
*/
private List<String> accomplishments;
/**
* Instantiates a new User.
*
* @param firstName the first name
* @param lastName the last name
* @param age the age
* @param isMarried the is married
* @param accomplishments the accomplishments
*/
public User(String firstName, String lastName, String age, boolean isMarried, List<String> accomplishments) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.isMarried = isMarried;
this.accomplishments = accomplishments;
}
}
By adding the @Getter annotation we told Lombok too, well, generate these for all the fields of the class.
The Value Objects pattern transforms values in our projects into real objects, giving us more type safety, hiding implementation, and giving a home to all related logic. That being said, we should always evaluate if the mentioned benefits outweigh the drawbacks of creating extra classes, which, in Java, implies extra source files and a rapidly growing size of the project.