In the previous post about the maps use in Selenium projects, I listed multiple reasons why using a map for a test like this is not a good idea:
@Test
public void createAccountTest() {
HashMap<String, String> userInfo = new HashMap<> ();
userInfo.put("username", USERNAME);
userInfo.put("password", PASSWORD);
userInfo.put("firstName", FIRST_NAME);
userInfo.put("lastName", LAST_NAME);
userInfo.put("birthDay", BIRTH_DAY);
userInfo.put("birthMonth", BIRTH_MONTH);
userInfo.put("birthYear", BIRTH_YEAR);
userInfo.put("streetName", STREET_NAME);
userInfo.put("city", CITY);
userInfo.put("postalCode", POSTAL_CODE);
userInfo.put("province", PROVINCE);
userInfo.put("country", COUNTRY);
HomePage homePage = new HomePage(driver);
homePage.open();
CreateAccountPage createAccountPage = homePage.goToCreateAccountPage();
createAccountPage.populateFields(userInfo);
createAccountPage.createAccount();
assertTrue(createAccountPage.isAccountCreated());
}
Using a map for storing together unrelated values (such as user credentials, name information, date of birth information, address) is something to avoid.
The question is what should we replace the map with.
We can replace it with an object of a new class named UserInfo.
It is possible to make the claim that all these details are user information.
public class UserInfo {
private String userName;
private String password;
private String firstName;
private String lastName;
private int birthDay;
private int birthMonth;
private int birthYear;
private String streetName;
private String city;
private String postalCode;
private String province;
private String country;
public UserInfo(String userName,String password,
String firstName,String lastName,
int birthDay,int birthMonth,int birthYear,
String streetName,String city,String postalCode,
String province,String country) {
this.userName = userName;
this.password = password;
this.firstName = firstName;
this.lastName = lastName;
this.birthDay = birthDay;
this.birthMonth = birthMonth;
this.birthYear = birthYear;
this.streetName = streetName;
this.city = city;
this.postalCode = postalCode;
this.province = province;
this.country = country;
}
public String getUserName() {
return userName;
}
public String getPassword() {
return password;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getBirthDay() {
return birthDay;
}
public int getBirthMonth() {
return birthMonth;
}
public int getBirthYear() {
return birthYear;
}
public String getStreetName() {
return streetName;
}
public String getCity() {
return city;
}
public String getPostalCode() {
return postalCode;
}
public String getProvince() {
return province;
}
public String getCountry() {
return country;
}
@Override
public String toString() {
return "UserInfo [userName=" + userName +
", password=" + password +
", firstName=" + firstName +
", lastName="+ lastName +
", birthDay=" + birthDay +
", birthMonth=" + birthMonth +
", birthYear=" + birthYear+
", streetName=" + streetName +
", city=" + city +
", postalCode=" + postalCode +
", province="+ province +
", country=" + country + "]";
}
@Override
public int hashCode() {
return Objects.hash(birthDay, birthMonth, birthYear,
city, country, postalCode,province, streetName,
firstName, lastName,
userName, password);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
UserInfo other = (UserInfo) obj;
return birthDay == other.birthDay &&
birthMonth == other.birthMonth &&
birthYear == other.birthYear &&
Objects.equals(city, other.city) &&
Objects.equals(country, other.country)&&
Objects.equals(firstName, other.firstName) &&
Objects.equals(lastName, other.lastName)&&
Objects.equals(password, other.password) &&
Objects.equals(postalCode, other.postalCode)&&
Objects.equals(province, other.province) &&
Objects.equals(streetName, other.streetName)&&
Objects.equals(userName, other.userName);
}
}
This class has a common structure:
constructor for building the new object
equals() method for comparing objects
hashCode() for generating a unique code for each object
toString() for providing a String value of the object
get() methods for all fields
Notice that there are no set methods defined.
This is done on purpose so that the object cannot be modified after being created.
The new class solves all issues introduced by the map:
all numeric values use a numeric type
UserInfo objects can be compared using the equals() method
if we need to validate values that are used for a new object, we can do this in the constructor
if we need to display the information of a UserInfo object, we can use the toString() method
we do not need to use lots of parameters in page methods
This last item is very important as the populateFields() method will change from
public class CreateAccountPage {
..................................
public void populateFields(HashMap<String, String> userInfo) {
typeValue(userNameId, userInfo.get("userName"));
typeValue(passwordId, userInfo.get("password"));
typeValue(firstNameId, userInfo.get("firstName"));
typeValue(lastNameId, userInfo.get("lastName"));
typeValue(birthDayId, userInfo.get("birthDay"));
typeValue(birthMonthId, userInfo.get("birthMonth"));
typeValue(birthYearId, userInfo.get("birthYear"));
typeValue(streetNameId, userInfo.get("streetName"));
typeValue(cityId, userInfo.get("city"));
typeValue(postalCodeId, userInfo.get("postalCode"));
typeValue(provinceId, userInfo.get("province"));
typeValue(countryId, userInfo.get("country"));
}
private void typeValue(By by, String value) {
WebElement element = driver.findElement(by);
element.sendKeys(value);
}
}
to
public class CreateAccountPage {
..................................
public void populateFields(UserInfo userInfo) {
typeValue(userNameId, userInfo.getUserName());
typeValue(passwordId, userInfo.getPassword());
typeValue(firstNameId, userInfo.getFirstName());
typeValue(lastNameId, userInfo.getLastName());
typeValue(birthDayId, String.valueOf(userInfo.getBirthDay()));
typeValue(birthMonthId, String.valueOf(userInfo.getBirthMonth()));
typeValue(birthYearId, String.valueOf(userInfo.getBirthYear()));
typeValue(streetNameId, userInfo.getStreetName());
typeValue(cityId, userInfo.getCity());
typeValue(postalCodeId, userInfo.getPostalCode());
typeValue(provinceId, userInfo.getProvince());
typeValue(countryId, userInfo.getCountry());
}
private void typeValue(By by, String value) {
WebElement element = driver.findElement(by);
element.sendKeys(value);
}
}
How is the UserInfo object created?
This object is created in the test method:
@Test
public void createAccountTest() {
UserInfo userInfo = new UserInfo(USERNAME, PASSWORD,
FIRST_NAME, LAST_NAME,
BIRTH_DAY, BIRTH_MONTH, BIRTH_YEAR,
STREET_NAME, CITY, POSTAL_CODE,
PROVINCE, COUNTRY);
HomePage homePage = new HomePage(driver);
homePage.open();
CreateAccountPage createAccountPage = homePage.goToCreateAccountPage();
createAccountPage.populateFields(userInfo);
createAccountPage.createAccount();
assertTrue(createAccountPage.isAccountCreated());
}
Using an object of the new UserInfo class instead of a map allows our test to be written fully in an object-oriented way.
There is an additional improvement that can be implemented.
You may say that it is too much to group in the UserInfo object
user credentials info
name info
birth day info
address info
How about having these separately?
Sure, we can do this too:
@Test
public void createAccountTest() {
UserInfo userInfo = new UserInfo(
new UserCredentials(USERNAME, PASSWORD),
new UserName(FIRST_NAME, LAST_NAME),
new UserBirthDay(BIRTH_DAY, BIRTH_MONTH, BIRTH_YEAR),
new UserAddress(STREET_NAME, CITY, POSTAL_CODE,
PROVINCE, COUNTRY));
HomePage homePage = new HomePage(driver);
homePage.open();
CreateAccountPage createAccountPage = homePage.goToCreateAccountPage();
createAccountPage.populateFields(userInfo);
createAccountPage.createAccount();
assertTrue(createAccountPage.isAccountCreated());
}
Notice that the test did not change at all.
The page class will not change either.
The only class that will need to be modified is UserInfo.
Its constructor will now get 4 parameters, one for each newly created class.
record is good java feature. But what about @Builder of lombok feature? It looks like
@Builder
public class UserInfo {
private String userName;
private String password;
private String firstName;
private String lastName;
private int birthDay;
private int birthMonth;
private int birthYear;
private String streetName;
private String city;
private String postalCode;
private String province;
private String country;
}
public UserInfo getUserInfo() {
return UserInfo.builder().userName(userName)...build();
}