Well-established tools like Mockito and EasyMock offer a powerful range of features for defining and interacting with mock objects. I use both tools heavily in my unit tests, and would recommend them to anyone looking for a mocking framework.
Sometimes, however, a real (i.e. non-proxied) collaborator is called for, or adding third-party libraries may not be an option. In most cases, a suitable object could be instantiated by calling the constructor and setting it as a dependency to the object under test. Suppose though that we have the following 'Footballer' class, and we want to write a test for the 'getAvgGoalsPerGame()' method:
We know that unless both of the 'gamesPlayed' and 'goalsScored' are initialised, a NullPointerException will be thrown, so we might initially write the test as follows:
This is all well and good, but when the number of fields that need to be set increases, the unit tests quickly become littered with code required to set up the mocks. In this case, it makes sense to perform the initialisation outside the test class.
--edit--
An alternative solution avoiding Reflection would be to create Builders for the classes to be mocked. An excellent article on the approach can be found here.
---------
A generic MockObjectPopulator is therefore required, and an outline of what this might look like is given in the example below:
Sometimes, however, a real (i.e. non-proxied) collaborator is called for, or adding third-party libraries may not be an option. In most cases, a suitable object could be instantiated by calling the constructor and setting it as a dependency to the object under test. Suppose though that we have the following 'Footballer' class, and we want to write a test for the 'getAvgGoalsPerGame()' method:
package com.rich.testingutils.mocking; public class Footballer { private String name; private int age; private Double salary; private Integer gamesPlayed; private Integer goalsScored; private Boolean isCaptain; public double getAvgGoalsPerGame(){ return (double) goalsScored / gamesPlayed; } // <getters and setters> // <hashcode> // <equals> }
We know that unless both of the 'gamesPlayed' and 'goalsScored' are initialised, a NullPointerException will be thrown, so we might initially write the test as follows:
package com.rich.testingutils.mocking; import org.junit.Test; import static junit.framework.Assert.assertEquals; public class FootballerTest { @Test public void testGetGameGoalsRatio() { Footballer footballer = new Footballer(); footballer.setGamesPlayed(14); footballer.setGoalsScored(7); assertEquals(Double.valueOf(0.5), footballer.getAvgGoalsPerGame()); } }
This is all well and good, but when the number of fields that need to be set increases, the unit tests quickly become littered with code required to set up the mocks. In this case, it makes sense to perform the initialisation outside the test class.
--edit--
---------
A generic MockObjectPopulator is therefore required, and an outline of what this might look like is given in the example below:
package com.rich.testingutils.mocking; import java.lang.reflect.Method; import java.util.Map; import static org.junit.Assert.fail; public class MockObjectPopulator { private DefaultValues defaultValues = new DefaultValues(); public void populateMockObject(Object obj) { populateMockObjectWithCustomValues(obj, null); } public void populateMockObjectWithCustomValues(Object obj, Map<String, Object> fieldMap) { try { for (Method f : obj.getClass().getDeclaredMethods()) { String methodName = f.getName(); if (methodName.startsWith("set")) { Class type = f.getParameterTypes()[0]; Object value; String fieldName = methodName.replace("set", ""); fieldName = fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1); if (fieldMap != null && fieldMap.containsKey(fieldName)) { value = fieldMap.get(fieldName); } else { value = getDefaultValueByType(type); } if (value != null) { f.invoke(obj, value); } } } } catch (Exception e) { e.printStackTrace(); fail(); } } private Object getDefaultValueByType(Class aClass) throws NoSuchMethodException { String typeName = aClass.getName(); Map<String, Object> defaultValuesMap = defaultValues.getDefaultValuesMap(); Object object = defaultValuesMap.get(typeName); return object; } }
In the above implementation, a map is constructed to associate the field names with their values, and this is then applied to the object passed as a parameter. Where the desired value for a field is not explicitly given in the map, a value is retrieved from the 'DefaultValues' class based on the declared type. An implementation of the DefaultValues class is given below. Note that it can be loaded with values either explicitly or through dependency injection.
package com.rich.testingutils.mocking; import java.util.HashMap; import java.util.Map; public class DefaultValues { private Map<String, Object> defaultValuesMap; public DefaultValues() { this.defaultValuesMap = new HashMap<String, Object>(); initMapWithPresets(); } public DefaultValues(Map<String, Object> defaultValuesMap) { this.defaultValuesMap = defaultValuesMap; initMapWithPresets(); } private void initMapWithPresets() { if (defaultValuesMap == null) { defaultValuesMap = new HashMap<String, Object>(); } for(PresetDefaultValue pdv : PresetDefaultValue.values()){ defaultValuesMap.put(pdv.typeName, pdv.defaultValue); } } public Map<String, Object> getDefaultValuesMap() { return defaultValuesMap; } public void setDefaultValuesMap(Map<String, Object> defaultValuesMap) { this.defaultValuesMap = defaultValuesMap; } private enum PresetDefaultValue { DOUBLE(Double.class.getName(), 0.0), STRING(String.class.getName(), ""), FLOAT(Float.class.getName(), Float.MIN_VALUE), LONG(Long.class.getName(), Long.MIN_VALUE), SHORT(Short.class.getName(), Short.MIN_VALUE), INTEGER(Integer.class.getName(), 0), BOOLEAN(Boolean.class.getName(), false), BYTE(Byte.class.getName(), Byte.MIN_VALUE), CHARACTER(Character.class.getName(), ' '); private String typeName; private Object defaultValue; private PresetDefaultValue(String typeName, Object defaultValue) { this.typeName = typeName; this.defaultValue = defaultValue; } } }Returning to our Footballer example, we can now write the unit test as follows:
package com.rich.testingutils.mocking; import org.junit.Test; import java.util.HashMap; import java.util.Map; import static junit.framework.Assert.assertEquals; public class FootballerTest { private MockObjectPopulator mockObjectPopulator = new MockObjectPopulator(); @Test public void testGetGameGoalsRatio() { Footballer footballer = new Footballer(); Map<String,Object> customValuesMap = new HashMap<String, Object>(); customValuesMap.put("gamesPlayed", 14); customValuesMap.put("goalsScored", 7); mockObjectPopulator.populateMockObjectWithCustomValues(footballer, customValuesMap); assertEquals(Double.valueOf(0.5), footballer.getAvgGoalsPerGame()); } }
All of the code in this example is now on GitHub, so please feel free to reuse anything that you have found useful.