Friday, December 30, 2011

Using Reflection To Create Mock Objects

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:

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--
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:

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.

5 comments:

  1. Good Idea, This is very valid point and having a custom Mock Creator especially a reusable one helps to quickly write unit test and test with real like input and parameters of domain object.

    Thanks
    Javin
    Top 10 multi-threading interview questions in Java

    ReplyDelete
  2. Hi Richard,

    thanks for this interesting post. This is surely a way of dealing with creation of objects, however, IMHO not the most recommended one. I would rather suggest using Test Data Builders approach (see http://nat.truemesh.com/archives/000714.html), which result in much cleaner and readable code.

    Reflection in test code? No thank you, unless this is really the only option (which is rare).

    --
    Regards,
    Tomek Kaczanowski
    http://practicalunittesting.com

    ReplyDelete
  3. Thanks for the feedback. Tomek - I take your point, and can see the value in the Builders approach. However, I still feel that there is room for the above solution as well. Even if there are a large number of classes to mock, this MockObjectPopulator can be used immediately, allowing the developer to start writing the tests without having to first invest in writing the individual Builders.

    That said, there are certainly instances where I would favour using a Builder to Reflection, and I've added the link to my post.

    ReplyDelete
  4. Thanks Richard, nice approach.

    Venkat

    ReplyDelete
  5. Very detailed and well presented article for easy understanding. Have really learnt a lot from it. Thanks.

    ReplyDelete