POJO and JavaBean testing using Bean-matchers

Last update: Jun 02, 2020

Project repo: https://github.com/gualtierotesta/blog-projects/tree/master/pojo-testing

The first reaction to the question “Should I unit test a data class?” is usually a big NO because there is no meaning to check a data class which contains boiler plate code (getter, setter, equals..) often generated by means of the IDE or by libraries like Lombok and Immutables.

Real world projects are, unfortunately, not so perfect and there are cases where unit testing a POJO or a JavaBean could make sense. Let me list some situations I have encountered.

1. Security sensitive fields

If the data class contains a security sensitive field like a password or an API token, this field should NOT be logged.

See what OWASP has to say about this problem: https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html#data-to-exclude

For this reason, the class should not include sensitive fields in the toString method.

2. Logging unfriendly fields

The data class can contain fields which are binary data or very long string. In this case, we should exclude these fields from the toString method to prevent them to bloat our logging files.

3. Non standard equals and hashCode methods

The standard way to implement an equals method is to compare, one by one, all fields in the class. Sometimes one field contains a “unique” identifier that makes other fields comparison useless or even dangerous.

For example, this field can contain the primary key column of the database record from which the POJO data has been extracted. In this case, the POJO equality could or should depend only on its primary key field and the equals (and hashCode) implementation should use just this field.

4. No args constructor (JavaBeans)

The JavaBean standard requires the presence of a no-args constructor, a constructor without parameters.

The compiler usually creates the no args constructor unless there are already other (non no-args ) constructors defined in the class.

At runtime, the library (for example, the JPA libraries) which rely on the no-args constructor presence will fail at runtime due to the missing no-args constructor.

One important remark: checking all above conditions using an unit test is a way to guarantee the correct behaviour of the data class along all project life. The class can be correctly implemented when created but, later, one developer, while adding a new field in the class, can wrongly regenerate the toString method, restoring the log of a password field, or forget to regenerate the equals/hashCode methods in the proper way.

Checking data class quality is also useful in legacy projects to detect improper classes definitions because unit class can be added aside the “main” classes, without disturbing the legacy code.

The Bean-matchers library

The Bean-matchers library, created by Orien Madgwick, let us test the class conditions to guarantee that all future changes in the class will not break them.

Project repo is at https://github.com/orien/bean-matchers

Let’s assume we have the following data class:

class BasicBean {
  private int id;
  private String string;
  private char[] password;
  private Long[] longArray;
  private String veryLongString;

  public LombokBean(final int id) {
    this.id = id;
  }

   // getter, setter, equals, hashCode, toString

Conditions we want to assure:

  1. the id field should be used to check equality and to generate the class hash
  2. the password field should not be logged because security sensible
  3. the veryLongString field should not be logged to avoid logging files bloating
  4. the class should have a no-args constructor to be used with JPA.

Using the Bean-matchers library, the test class can be:

@Test
public void testTheClassIsGoodJavaBean() {
  MatcherAssert.assertThat(BasicBean.class,
    CoreMatchers.allOf(
      // This is Java Bean so we want an empty constructor
      BeanMatchers.hasValidBeanConstructor(),
      // All fields should have getter and setter
      BeanMatchers.hasValidGettersAndSetters(),
      // Only the 'id' field in hashcode and equals
      BeanMatchers.hasValidBeanHashCodeFor("id"),
      BeanMatchers.hasValidBeanEqualsFor("id"),
      // Password and veryLongString fields should not 
      // be included in the toString method
      BeanMatchers.hasValidBeanToStringExcluding(
          "password", "veryLongString")
    ));
}

The Beans-matchers library create an instance of our BasicBean class and checks if all conditions we have defined on

  • the constructor
  • the getters and setters,
  • the equals method
  • the hashCode method
  • the toString method

are valid otherwise the test fails.

A test failure will alert us whenever a change in the BasicBean class breaks the above conditions. It is a kind of safety net.

For Lombok users

Using Lombok, we can implement our bean conditions using Lombok annotations. The following code is the equivalent version of the plain Java version reported above:

@Data
@NoArgsConstructor
@EqualsAndHashCode(of = "id")
@ToString(exclude = {"password", "veryLongString"})
public class LombokBean {
  private int id;
  private String string;
  private char[] password;
  private Long[] longArray;
  private String veryLongString;

  public LombokBean(final int id) {
    this.id = id;
  }
}

When using Lombok, I believe there is no need to have a unit test to check our bean conditions because, in general, we should not test third party libraries (Lombok in this case), but having it will not damage your project 😉

Final remarks

Another possible benefit of testing data classes, especially in legacy projects, is that their code coverage is easily 100%. This should not be the main purpose (remember, there is no meaning to test getter and setter) but a side effect which I’ve found useful: having the data classes at 100%, the low test coverage is due to the logic classes, the classes which contains the code which implement the application logic. They should be the main target of any test!

Unit testing Java data classes immutability with the Mutability Detector

Updated post can be found on my new blog site.

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.

The 5 Java logging rules

Logging is a critical factor that should be always kept into account during the software development.

When something bad happens in production, the log files are usually the starting point of our fault analysis. And, often, they are the only information in our hands to understand what is happened and which is the root cause of the problem.

It is so very important to have the required information logged properly.

The following five logging rules are a way to check and, possibly, improve how we handle the logging in our code.

Please note that we will not discuss how to configure a logging engine nor we will compare them to each other.

Rule 1. Logging is for readers

The logging messages should be meaningful to who will read the log files, not only to who wrote the (logging) code.

It seem a very obvious rule but it is often violated.

For example, let’s consider a log message like the following

ERROR: Save failure - SQLException .....

Saving what? This message could mean something for the developer but it is completely useless for the poor guy which is looking at the production problem.

Much better message is

ERROR: Save failure- Entity=Person, Data=[id=123 surname="Mario"] - SQLException....

which explains what you wanted to save (here a Person, a JPA entity) and the relevant contents of the Person instance. Please note the word relevant, instead of the world all: we should not clutter log files with useless info like the complete print of all entity fields. Entity name and its logical keys are usually enough to identify a record in a table.

Rule 2. Match the logging levels with the execution environment

All logging facades and engines available in the Java ecosystem have the concept of logging level (ERROR, INFO…), with the possibility to filter out messages with too low level.

For example, Java util logging uses the following levels: SEVERE, WARN, INFO, FINE, FINER, FINEST (+ CONFIG and OFF).

On the contrary, the two most popular logging facade, Apache Commons Logging and SLFJ, prefer the following levels: FATAL, ERROR, WARN, INFO, DEBUG, TRACE.

Logging level filtering should depends on which stage of the development is your code: logging level in the production should not be the same as in test/integrations environments.

Moreover, logging level should also depends on the code owner. In general our own application code should have a more detailed logging compared to any third party library we are using. There is no big meaning to see, for example, Apache Commons debug messages in our log files.

I usually configure the logging as following:

  • Production: INFO level for my code and WARN for third party libraries.
  • Test / Integration: DEBUG level for my code and WARN (or INFO if needed) for third party libraries.
  • Development: whatever make sense

Note: I personally discourage the use of the TRACE/FINEST level (and I’m not alone, see for example here). I don’t see big difference between DEBUG and TRACE and it is usually difficult for the young team members to decide which one, DEBUG or TRACE, to use. Following the Kiss principle, I suggest to use ERROR, WARN, INFO and DEBUG levels only.

Rule 3. Remove coding help logging before the commit.

While coding, we usually add logging messages, using the logger or the System.out, in our code for a better understanding of what it is happening in our application during execution /debugging sessions.

Something like:

    void aMethod(String aParam) {
        LOGGER.debug(“Enter in aMethod”);
        if (“no”.equals(aParam)) {
           LOGGER.debug(“User says no”);
          ….

The main purpose of these messages is to trace application behaviour by showing which method is invoked and by dumping internal variables and method parameters values. Quite popular among non TDD devotes.

Unfortunately these messages do not have usually big meaning once the code has been released (to test and then production).

So this rule simply says: once you have finished to develop, remove all temporary and unnecessary logging messages just before committing the code to the SCM system (git, svn..) in use.

This rule does not require to remove all DEBUG messages but only the ones that do not have meaning once the application is completed and released; in other words when we are reasonably sure that the application is working properly.

Rule 4: Check log level before logging DEBUG messages

According to Rule 2, in production logs we will show ERROR, WARN, INFO messages only but in our code we can have many DEBUG messages that should not affect production execution.

Every time you want to log a DEBUG message (all the ones which remain after rule 3), add in front a check if DEBUG logging is enabled:

    if ( LOGGER.isDebugEnabled() ) {
        LOGGER.debug (…….)
    }

This will prevent you code to build the log messages and call the logger. It is for efficiency in the program execution at production.

Rule 5: Know your logger

How we use the logger methods can have a significant cost:

  • To build the message string
  • to collect the data to be included in the message string

We should review the javadoc of the selected logging facade/engine and understand the most efficient way to use its logger.

For example, we could create a message like this:

    LOGGER.info(“Person name is “ + person.getName());

which creates unnecessary strings instances.

Using SLF4J, the correct use is :

    LOGGER.info(“Person name is {}“, person.getName());

where format string is constant and the final message is build only if logging is enabled. See here for more details.