Resolving God Objects:
Façade Tests
Context
This article is part of the God object resolution series. To illustrate it, we use a running example through the whole series. So far, we dispatched the logics, the state, and the tests of the God object into new features. We also transformed it into a façade to use it as a feature aggregator. This article is about testing this façade, the right way.
Problem
The God object can now be used as a proper façade... or can we? Actually, we didn't test anything, so we cannot ensure it does it properly.
Solution
A façade is a specific wrapper, and you can find a whole article about how to test them. Here, we will only summarize the idea and give the result.
Each method of the façade should be tested on these criteria:
- the parameters are correctly transferred to the feature method ;
- the result is correctly transferred from the feature method ;
- if no parameter nor result should be transferred, the feature method is correctly called.
If we take the first test, hire an employee, here it is:
@Test
public void testHireEmployeeIsCorrectlyMapped() {
// Set the expected parameter & return values
Employee expectedEmployee = new TestEmployee();
int expectedEmployeeID = 123;
// Retrieve the actual parameter & return values
Employee[] actualEmployee = {null};
Employer employer = new TestEmployer() {
@Override
public int hireEmployee(Employee employee) {
actualEmployee[0] = employee;
return expectedEmployeeID;
}
};
EmployeeDetails details = new TestEmployeeDetails();
SalarySettings salarySettings = new TestSalarySettings();
EmployeeManager manager = new EmployeeManager(employer, details, salarySettings);
int actualEmployeeID = manager.hireEmployee(expectedEmployee);
// Test
assertEquals(expectedEmployee, actualEmployee[0]);
assertEquals(expectedEmployeeID, actualEmployeeID);
}
The first section of the test provides the values we will transfer to/from the feature implementation.
The second section creates all the required implementations, where each TestXxx
is a default implementation of the corresponding class.
These default implementations throw a RuntimeException("not implemented")
, which can be factored into a NotImplementedException
(you can also find existing ones in some libraries, like in the Apache Commons).
In these implementations, we only override the method that should be called, hireEmployee(employee)
here.
The last section does the assertions to ensure that the received values are the ones we sent.
In our running example, we mentionned the possibility to adapt an Employee
into a Contract
.
This is an adapter aspect, which is another kind of wrapper.
The impacted tests should include any adaptation we do to ensure that we do it properly.
We didn't do it, though, so we remain on a simple façade testing.
These are all the tests we wrote at the end:
public class EmployeeManagerTest {
@Test
public void testHireEmployeeIsCorrectlyMapped() {
// Set the expected parameter & return values
Employee expectedEmployee = new TestEmployee();
int expectedEmployeeID = 123;
// Retrieve the actual parameter & return values
Employee[] actualEmployee = {null};
Employer employer = new TestEmployer() {
@Override
public int hireEmployee(Employee employee) {
actualEmployee[0] = employee;
return expectedEmployeeID;
}
};
EmployeeDetails details = new TestEmployeeDetails();
SalarySettings salarySettings = new TestSalarySettings();
EmployeeManager manager = new EmployeeManager(employer, details, salarySettings);
int actualEmployeeID = manager.hireEmployee(expectedEmployee);
// Test
assertEquals(expectedEmployee, actualEmployee[0]);
assertEquals(expectedEmployeeID, actualEmployeeID);
}
@Test
public void testFireEmployeeIsCorrectlyMapped() {
// Set the expected parameter & return values
Employee expectedEmployee = new TestEmployee();
int expectedEmployeeID = 123;
// Retrieve the actual parameter & return values
int[] actualEmployeeID = {0};
Employer employer = new TestEmployer() {
@Override
public Employee fireEmployee(int employeeID) {
actualEmployeeID[0] = employeeID;
return expectedEmployee;
}
};
EmployeeDetails details = new TestEmployeeDetails();
SalarySettings salarySettings = new TestSalarySettings();
EmployeeManager manager = new EmployeeManager(employer, details, salarySettings);
Employee actualEmployee = manager.fireEmployee(expectedEmployeeID);
// Test
assertEquals(expectedEmployee, actualEmployee);
assertEquals(expectedEmployeeID, actualEmployeeID[0]);
}
@Test
public void testSetEmployeeIsCorrectlyMapped() {
// Set the expected parameter & return values
Employee expectedOldEmployee = new TestEmployee();
Employee expectedNewEmployee = new TestEmployee();
int expectedEmployeeID = 123;
// Retrieve the actual parameter & return values
Employee[] actualNewEmployee = {null};
int[] actualEmployeeID = {0};
Employer employer = new TestEmployer();
EmployeeDetails details = new TestEmployeeDetails() {
@Override
public Employee setContract(int employeeID, Employee employee) {
actualEmployeeID[0] = employeeID;
actualNewEmployee[0] = employee;
return expectedOldEmployee;
}
};
SalarySettings salarySettings = new TestSalarySettings();
EmployeeManager manager = new EmployeeManager(employer, details, salarySettings);
Employee actualOldEmployee = manager.setEmployee(expectedEmployeeID, expectedNewEmployee);
// Test
assertEquals(expectedNewEmployee, actualNewEmployee[0]);
assertEquals(expectedOldEmployee, actualOldEmployee);
assertEquals(expectedEmployeeID, actualEmployeeID[0]);
}
@Test
public void testGetEmployeeIsCorrectlyMapped() {
// Set the expected parameter & return values
Employee expectedEmployee = new TestEmployee();
int expectedEmployeeID = 123;
// Retrieve the actual parameter & return values
int[] actualEmployeeID = {0};
Employer employer = new TestEmployer();
EmployeeDetails details = new TestEmployeeDetails() {
@Override
public Employee getContract(int employeeID) {
actualEmployeeID[0] = employeeID;
return expectedEmployee;
}
};
SalarySettings salarySettings = new TestSalarySettings();
EmployeeManager manager = new EmployeeManager(employer, details, salarySettings);
Employee actualEmployee = manager.getEmployee(expectedEmployeeID);
// Test
assertEquals(expectedEmployee, actualEmployee);
assertEquals(expectedEmployeeID, actualEmployeeID[0]);
}
@Test
public void testSetAddressIsCorrectlyMapped() {
// Set the expected parameter & return values
Address expectedNewAddress = new TestAddress();
int expectedEmployeeID = 123;
// Retrieve the actual parameter & return values
Address[] actualNewAddress = {null};
int[] actualEmployeeID = {0};
Employer employer = new TestEmployer();
EmployeeDetails details = new TestEmployeeDetails() {
@Override
public void setAddress(int employeeID, Address address) {
actualEmployeeID[0] = employeeID;
actualNewAddress[0] = address;
}
};
SalarySettings salarySettings = new TestSalarySettings();
EmployeeManager manager = new EmployeeManager(employer, details, salarySettings);
manager.setAddress(expectedEmployeeID, expectedNewAddress);
// Test
assertEquals(expectedNewAddress, actualNewAddress[0]);
assertEquals(expectedEmployeeID, actualEmployeeID[0]);
}
@Test
public void testGetAddressIsCorrectlyMapped() {
// Set the expected parameter & return values
Address expectedAddress = new TestAddress();
int expectedEmployeeID = 123;
// Retrieve the actual parameter & return values
int[] actualEmployeeID = {0};
Employer employer = new TestEmployer();
EmployeeDetails details = new TestEmployeeDetails() {
@Override
public Address getAddress(int employeeID) {
actualEmployeeID[0] = employeeID;
return expectedAddress;
}
};
SalarySettings salarySettings = new TestSalarySettings();
EmployeeManager manager = new EmployeeManager(employer, details, salarySettings);
Address actualAddress = manager.getAddress(expectedEmployeeID);
// Test
assertEquals(expectedAddress, actualAddress);
assertEquals(expectedEmployeeID, actualEmployeeID[0]);
}
@Test
public void testSetPhoneNumberIsCorrectlyMapped() {
// Set the expected parameter & return values
String expectedNewPhone = "0123456789";
int expectedEmployeeID = 123;
// Retrieve the actual parameter & return values
String[] actualNewPhone = {null};
int[] actualEmployeeID = {0};
Employer employer = new TestEmployer();
EmployeeDetails details = new TestEmployeeDetails() {
@Override
public void setPhoneNumber(int employeeID, String phoneNumber) {
actualEmployeeID[0] = employeeID;
actualNewPhone[0] = phoneNumber;
}
};
SalarySettings salarySettings = new TestSalarySettings();
EmployeeManager manager = new EmployeeManager(employer, details, salarySettings);
manager.setPhoneNumber(expectedEmployeeID, expectedNewPhone);
// Test
assertEquals(expectedNewPhone, actualNewPhone[0]);
assertEquals(expectedEmployeeID, actualEmployeeID[0]);
}
@Test
public void testGetPhoneNumberIsCorrectlyMapped() {
// Set the expected parameter & return values
String expectedPhone = "0123456789";
int expectedEmployeeID = 123;
// Retrieve the actual parameter & return values
int[] actualEmployeeID = {0};
Employer employer = new TestEmployer();
EmployeeDetails details = new TestEmployeeDetails() {
@Override
public String getPhoneNumber(int employeeID) {
actualEmployeeID[0] = employeeID;
return expectedPhone;
}
};
SalarySettings salarySettings = new TestSalarySettings();
EmployeeManager manager = new EmployeeManager(employer, details, salarySettings);
String actualPhone = manager.getPhoneNumber(expectedEmployeeID);
// Test
assertEquals(expectedPhone, actualPhone);
assertEquals(expectedEmployeeID, actualEmployeeID[0]);
}
@Test
public void testSetSalaryIsCorrectlyMapped() {
// Set the expected parameter & return values
double expectedNewSalary = 1000;
int expectedEmployeeID = 123;
// Retrieve the actual parameter & return values
double[] actualNewSalary = {0};
int[] actualEmployeeID = {0};
Employer employer = new TestEmployer();
EmployeeDetails details = new TestEmployeeDetails();
SalarySettings salarySettings = new TestSalarySettings() {
@Override
public void setSalary(int employeeID, double salary) {
actualEmployeeID[0] = employeeID;
actualNewSalary[0] = salary;
}
};
EmployeeManager manager = new EmployeeManager(employer, details, salarySettings);
manager.setSalary(expectedEmployeeID, expectedNewSalary);
// Test
assertEquals(expectedNewSalary, actualNewSalary[0], 0);
assertEquals(expectedEmployeeID, actualEmployeeID[0]);
}
@Test
public void testGetSalaryIsCorrectlyMapped() {
// Set the expected parameter & return values
double expectedSalary = 1000;
int expectedEmployeeID = 123;
// Retrieve the actual parameter & return values
int[] actualEmployeeID = {0};
Employer employer = new TestEmployer();
EmployeeDetails details = new TestEmployeeDetails();
SalarySettings salarySettings = new TestSalarySettings() {
@Override
public double getSalary(int employeeID) {
actualEmployeeID[0] = employeeID;
return expectedSalary;
}
};
EmployeeManager manager = new EmployeeManager(employer, details, salarySettings);
double actualSalary = manager.getSalary(expectedEmployeeID);
// Test
assertEquals(expectedSalary, actualSalary, 0);
assertEquals(expectedEmployeeID, actualEmployeeID[0]);
}
@Test
public void testRaiseSalaryIsCorrectlyMapped() {
// Set the expected parameter & return values
double expectedAmount = 1000;
int expectedEmployeeID = 123;
// Retrieve the actual parameter & return values
double[] actualAmount = {0};
int[] actualEmployeeID = {0};
Employer employer = new TestEmployer();
EmployeeDetails details = new TestEmployeeDetails();
SalarySettings salarySettings = new TestSalarySettings() {
@Override
public void raiseSalary(int employeeID, double amount) {
actualEmployeeID[0] = employeeID;
actualAmount[0] = amount;
}
};
EmployeeManager manager = new EmployeeManager(employer, details, salarySettings);
manager.raiseSalary(expectedEmployeeID, expectedAmount);
// Test
assertEquals(expectedAmount, actualAmount[0], 0);
assertEquals(expectedEmployeeID, actualEmployeeID[0]);
}
}
At this point, the façade is tested as such. Only the deprecated constructor is not covered, so the coverage is not 100%. However, this is not an issue because it should disappear in the long term, at which time the coverage will be 100%. But before to remove it, we must refactor the code using it.