Menu

Home

Willemijn Wouters Mestrez Matthieu

Unitils Object validation Module

Purpose

The purpose of this module is to 'automatically' test the basics of java classes such as getters and setters, constructors, equals and hashcode methods and other basic compliency rules.
Other purpose is to have all this code covered without hard work.

After having done a small configuration of your project, you simply have to annotate an ObjectValidator with @ObjectValidationRules and tell him which classes he has to verify

Concepts

  • Unitils (refer to unitils documentation : http://www.unitils.org/summary.html)
  • Rule : A rule is a java class which purpose is to validate something on another class, it could be verify that the toString method is well written and overriden for instance.
  • RuleCollection : Is a set of rules used as configuration of your project. Out of the box there is the SunBeanRules that verifies that objects are written with Sun compliency.
  • EqualsHashCodeValidator : Utility that will check that the equals and hashCode uses all the specified fields, it will cover the conditions of the equals and hashCode totally.

Basic configuration

For this to be used you need a basic understanding on how unitils works.
Simply put, a unitils.properties files has to be found at the root of test/resources folder of the project you want to test,
and annotate your test class with @RunWith(UnitilsJUnit4TestClassRunner.class).

This additional maven dependency has to be added :

Validation Module load artifact (maven)

    <dependency>
        <groupId>org.unitils.objectvalidation</groupId>
        <artifactId>unitils-objectvalidation</artifactId>
        <version>1.1.4</version>
    </dependency>

Validation Module project config

Please create unitils-local.properties, and add objectvalidation to unitils.modules. Code as following:

unitils.modules=objectvalidation

unitils.module.objectvalidation.className=org.unitils.objectvalidation.ValidationModule
unitils.module.objectvalidation.runAfter=
unitils.module.objectvalidation.enabled=true

Examples

This first example tests three objects and checks their equals and hashcode method

@RunWith(UnitilsJUnit4TestClassRunner.class)
public class ValidatesBeanWithAnnotationTest {

    @ObjectValidationRules
    private ObjectValidator objectValidator;

    @Test
    public void validateBeans() {
        objectValidator.
             classToValidate(ValidBean.class).checkingAllPossibilities().withAllFields().
             classToValidate(ValidBeanWithByteArray.class).checkingAllPossibilities().withFieldNames("firstField", "secondField", "byteArray").
             classToValidate(ValidBeanOnlyId.class).checkingAllPossibilities().withFieldNames("id").
             validate();
   }
}

This example is to show how to test a simple utility method

@RunWith(UnitilsJUnit4TestClassRunner.class)
public class ValidatesUtilityClassWithAnnotationTest {

    @ObjectValidationRules(replacementRules = {UtilityClassRules.class})
    private ObjectValidator utilityValidator;

    @Test
    public void validateUtilsClass() {
        utilityValidator.
            classToValidate(Utils.class).
            withoutCheckingAllPossibilities().
            validate();
    }

}

Existing Rules

Out of the box it exists two basic rule collection :

SunBeanRules, it will check that :

  • The Equals method is complient to Sun rules (Reflexive, Symmetric, Transitive, Consistent, Fails in the case of null value, Inequality when other instance of object, inequality when other object)
  • The HashCode method is complient to Sun rules : Same objects must produce the same hashCode number, Equals object have the same number (Note than an object is not obligated to have a different hashCode in the case of different value, this logic is not developped)
  • That the getter and the setter are complient : validate that every field is accessible with a getter and a setter, also that if we set an object to the field that the getter will return it
  • That the class must be serializable and the serialVersionUID is defined.
  • That there is a public empty constructor

    org.unitils.objectvalidation.rules.collection=org.unitils.objectvalidation.rulescollection.SunBeanRules
    

SunBeanRulesWithMocks does almost the same as the SunBeanRules, but it won't generate the entire object graph. It creates an object with primitives and mocks.

org.unitils.objectvalidation.rules.collection=org.unitils.objectvalidation.rulescollection.SunBeanRulesWithMocks

UtilityClassRules, it will check that :

  • The class only have one private constructor
  • That the class only have static methods
  • That the class is final

    org.unitils.objectvalidation.rules.collection=org.unitils.objectvalidation.rulescollection.UtilityClassRules
    

Equals and hashCode validator

You can choose to use or not the equals and hashCode validator. If you want to have maximum coverage on your overriden equals and hashCode methods then it's advised to use it.

If you want to validate the class equals and hashCode method, you need to implement it this way :

objectValidator.classToValidate(ValidBean.class).
    checkingAllPossibilities().
    withAllFields().
    validate();

This will take class ValidBean and create a different object per field and compare them against each other to see if the equals and hashCode method are complient.

If you don't want to validate it (for example in the case of an utility method) :

objectValidator.classToValidate(Utils.class).
        withoutCheckingAllPossibilities().
        validate();

What if in your class Person you have a simple identity equals and don't look at other fields ?

objectValidator.classToValidate(Person.class).
    checkingAllPossibilities().
    withFieldNames("id").
    validate();

You can also add several classes at the same time :

objectValidator.
         classToValidate(ValidBean.class).checkingAllPossibilities().withAllFields().
         classToValidate(ValidBeanWithByteArray.class).checkingAllPossibilities().withAllFields().
         classToValidate(ValidBeanOnlyId.class).checkingAllPossibilities().withFieldNames("id").
         validate();

Object Creator and generators

For the objectvalidation being able to work it exists an ObjectCreator that will create randomized instance of classes.
They are defined inside the RulesCollection, it's a field. For the SunBeanRules for example the org.unitils.objectvalidation.objectcreator.ObjectCreatorFactory.createDefaultObjectCreator() is used.

If you use something else than the default types of object (for example use joda.time) then you need to override this objectCreator like this :

private static ObjectCreator createProjectObjectCreator() {
    return ObjectCreatorFactory.createObjectCreator(new Generator[] {new ProjectCompositeGenerator()});
}

public static class ProjectCompositeGenerator extends CompositeGenerator {
    public ProjectCompositeGenerator() {
        addGenerators(new Generator[] {new JodaTimeGenerator(),
                                       new EnumGenerator(),
                                       new PrimitiveGenerator(),
                                       new CollectionGenerator(),
                                       new BuilderGenerator(),
                                       new LastResortGenerator()});
    }
}

The jodaTimeGenerator must implement org.unitils.objectvalidation.objectcreator.generator.Generator and generate random objects.

    public static class JodaTimeGenerator implements Generator {

    /**
     * Returns now.
     */
    @Override
    public Object generateObject(Class<?> clazz, List<Object> input, List<Class<?>> inputClasses, List<TreeNode> genericSubTypes) throws Exception {
        if (DateTimeZone.class.equals(clazz)) {
            return DateTimeZone.getDefault();
        } else if (DateTime.class.equals(clazz)) {
            return DateTime.now();
        }

        return null;
    }

Extending Rules

If in your project you have specific rules that you want to test ? Check that on each class you have overriden the toString method for example ?

You simply create a class implementing org.unitils.objectvalidation.Rule like this :

public class ToStringHasToBeOverridenRule implements Rule {

    @Override
    public void validate(Class<?> classToValidate) {
        Method toStringMethod;
        try {
            toStringMethod = classToValidate.getDeclaredMethod("toString");
            assertNotNull("toString method has to be Overriden", toStringMethod);
        } catch (SecurityException e) {
            fail("Could not be read due to security reasons.\n" + e.getMessage());
        } catch (NoSuchMethodException e) {
            fail("Does not contain toString method. It should be overriden.");
        }
    }

}

Rules Collection

If the rules collection SunBeanRules and UtilityClassRules don't fit your purpose you simply can redefine your rule collection by implementing org.unitils.objectvalidation.ObjectValidationRulesCollection :

You then need to override the method public List<Rule> getRules(), provide a list of rules for your project, for instance :

@Override
public List<Rule> getRules() {

    return Arrays.asList(new EqualsComplientRule(objectCreator, objectCloner) ,
                         new HashCodeComplientRule(objectCreator, objectCloner),
                         new GetterRctComplientRule(objectCreator, objectCloner),
                         new NoSetterRule(),
                         new PrivateConstructorWithBuilderRule(),
                         new ToStringHasToBeOverridenRule());
}

You see here the use of the objectCreator (see 6. in this document) and the object cloner, you can use the default ones :

public ProjectRules() {
    objectCreator = ObjectCreatorFactory.createDefaultObjectCreator();
    objectCloner = ObjectClonerFactory.createDefaultCloner();
}

Or define your own.

Then you have to override the method public EqualsHashCodeValidator getEqualsHashCodeValidator()

Where you can use the default one :

return new EqualsHashCodeValidator(objectCreator, objectCloner);

or redefine your own.

Replacement rules & Additional Rules

If on a project you need for one or several test to don't use the specified rule collection (in the unitils.properties file) and use another instead you can define that at annotation time :

@ObjectValidationRules(replacementRules = SunBeanRules.class)

If you want to run the configured one (in the unitils.properties file) and also another set of rules in addition use this parameter of the annotation :

@ObjectValidationRules(additionalRules = SunBeanRules.class)