2016. november 4., péntek

Using reflection in setup phase of unit testing

It is  a common practice to use mock framework in the setup phase of a unit test, in order to set the internal state of the dependent objects. Sometimes it is though easier to set a value of a given field directly, in order to test the class accordingly.

For modifying field value via reflection, I used the reflection utility classes from the org.apache.commons.lang3.reflect package.

In my example I had a class, responsible to holding the application configuration, which is loaded from an XML file. The default location of the configuration file is hard-coded into the class. I wanted however test the loadConfig method, with different test configuration files, in order to check all possible configuration cases.


public class ConfigurationHolder {

    private static final String DEFAULT_CONFIGURATION_FILE_LOCATION = "./src/main/conf/cleaner.xml";

    private final String configurationFileLocation;

    private CleanerConfiguration cleanerConfiguration;

    private final static ConfigurationHolder instance = new ConfigurationHolder();

    private ConfigurationHolder() {
        this.configurationFileLocation = DEFAULT_CONFIGURATION_FILE_LOCATION;
    }

    public CleanerConfiguration getConfiguration() {
        if (cleanerConfiguration == null) {
            loadConfig();
        }

        return cleanerConfiguration;
    }

    private void loadConfig() {
        try {
            JAXBContext jaxbContext = JAXBContext.newInstance(CleanerConfiguration.class);
            Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
            File file = new File(configurationFileLocation);
            cleanerConfiguration = (CleanerConfiguration) jaxbUnmarshaller.unmarshal(file);
        } catch (JAXBException e) {
            throw new RuntimeException("Problem with cleaner configuration: " + e.getMessage());
        }
    }

    public static ConfigurationHolder getInstance() {
        return instance;
    }

}

With using mock framework I would have had design following possibilities:
  • creating a class, like ConfigurationFileLocationProvider or ConfigurationFileProvider to deliver the location of the file, or the file itself, and use the class in ConfigurationHolder.
    In this case I believe, I would have introduced a dependency just to make testing possible.
     
  • Passing the file location in the constructor. In this case I would have lost the possibility to use the class as singleton. 

So I decide to keep the simple implementation, and use reflection for the unit test. I decided to use the commons reflections package, while it seams to be a more sophisticated solution as the one from Spring framework.

As I wanted to set the value of a simple private field, I implemented the following method in the unit test class:


private ConfigurationHolder createConfigurationHolder(String configFileLocation) throws IllegalAccessException {
    ConfigurationHolder cr = ConfigurationHolder.getInstance();
    FieldUtils.writeField(cr, "configurationFileLocation", configFileLocation, true);
    FieldUtils.writeField(cr, "cleanerConfiguration", null, true);
    return cr;
}

In my test case I use this method to create a new instance, with the given file location

@Test
public void testGetConfiguration() throws Exception {
    String configFileLocation = "./src/test/conf/cleaner-config-test.xml";
    ConfigurationHolder cr = createConfigurationHolder(configFileLocation);

    CleanerConfiguration configuration = cr.getConfiguration();
    Assert.assertThat(configuration, notNullValue());
}


As always, you need to consider what brings you to modify the state for unit test, and which possible problems can you face with.

Advantages.
  • you can change the internal state of your class without adding unwanted dependencies to it.
  • you can test objects without setter methods like classes used for XML or JSON data modelling (with @XmlRootElement or @JsonAutoDetect annotation). These classes are generated usually without setter methods, and instances get filled up via reflection.  

Disadvantages
  • as in case of the solution, where unit testing is done via mock framework, you need to design the class carefully, to avoid restrictions regarding reflection. In this case, I had to use the configurationFileLocation property, while, the DEFAULT_CONFIGURATION_FILE_LOCATION constant can not be modified by reflection.
  • As name of the field to be modified via reflection is hard-coded into the unit test case, you need to be careful when refactoring the class. I do not see it as a big risk, however, while you need to run unit test during the refactoring anyway, so you will be forced the change the relevant test cases as well.  

Further useful methods from the FieldUtils class:
  • removeFinalModifier() makes it possible to change even final fields
  • writeStaticField() makes it possible to change even static fields



Nincsenek megjegyzések:

Megjegyzés küldése