Matthieu Vergne's Homepage

Last update: 14/10/2019 21:30:41

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:

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.