Unit testing Java data classes immutability with the Mutability Detector
In all our project, we use data classes which, by definition, contain data (fields) but no (business) logic.
According to the best coding practices, a data class should preferably be immutable because immutability means thread safety. Main reference here is Joshua Bloch’s Effective Java book; this Yegor Bugayenko’s post is also very interesting reading.
An immutable class has several interesting properties:
- it should be not sub-classable (i.e. it should be final or it should have a static factory method and a private constructor)
- all fields should be private (to prevent direct access)
- all fields should be written once (at instance creation time) (i.e. they should be final and without setters)
- all mutable type (like java.util.Date) fields should be protected to prevent client write access by reference
An example of immutable class is the following:
public final class ImmutableBean { private final String aStr; private final int anInt; public ImmutableBean(String aStr, int anInt) { this.aStr = aStr; this.anInt = anInt; } public String getAStr() { return aStr; } public int getAnInt() { return anInt; } }
Note: as frequent in Java, there is a lot of boilerplate code which hides the immutability definitions.
Libraries like Project Lombok makes our life easier because we can use the @Value annotation to easily define an immutable class as follows:
@Value public class LombokImmutableBean { String aStr; int anInt; }
which is a lot more more readable.
Should we (unit) test a class to check its immutability?
In a perfect world, the answer is no.
With the help of our preferred IDE automatic code generation features or with libraries like Lombok it is not difficult to add immutability to a class.
But in a real world, human errors can be happen, when we create the class or when we (or may be a junior member of the team) modify the class later on. What happen if a new field is added without final and a setter is generated by using IDE code generator? The class is no more immutable.
It is important to guarantee that the class is and remains immutable along all project lifetime.
And with the help of the Mutability Detector we can easily create a test to check the immutability status of a class.
As usual, Maven/Gradle dependencies can be found on Maven Central.
To test our ImmutableBean we can create the following jUnit test class:
import static org.mutabilitydetector.unittesting.MutabilityAssert.assertImmutable; public class ImmutableBeanTest { @Test public void testClassIsImmutable() { assertImmutable(ImmutableBean.class); } }
the test will fail if the class is not immutable.
For example, if a field is not final and it has a setter method, the test fails and the error message is very descriptive:
org.mutabilitydetector.unittesting.MutabilityAssertionError: Expected: it.gualtierotesta.testsolutions.general.beans.ImmutableBean to be IMMUTABLE but: it.gualtierotesta.testsolutions.general.beans.ImmutableBean is actually NOT_IMMUTABLE Reasons: Field is not final, if shared across threads the Java Memory Model will not guarantee it is initialised before it is read. [Field: aStr, Class: it.gualtierotesta.testsolutions.general.beans.ImmutableBean] Field [aStr] can be reassigned within method [setaStr] [Field: aStr, Class: it.gualtierotesta.testsolutions.general.beans.ImmutableBean]
The complete project can be found on my Test Solutions gallery project on GitHub. See module general.
The approach I suggest is to use Lombok without any immutability test. If Lombok cannot be used (for example in a legacy project), use the Mutability Detector to assert that the class is really immutable.
Reference: | Unit testing Java data classes immutability with the Mutability Detector from our JCG partner Gualtiero Testa at the Gualtiero Testa blog. |