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.

Tutorial: logging during tests

Logging is a popular solution to show what the software is doing while it is running.

But what happens to the logging when we are unit testing our application with jUnit/TestNG ?

During the automated tests execution, we are usually not interested to see the logging messages because our main interest is the test results.

It would be nice to be able to disable logging messages during standard automated tests.

On the contrary, there are some cases where logging messages can be useful at test time. A typical example is when we are coding missing tests for some legacy code we do not want to touch before having a good test coverage in place. In this case logging messages on the console can help us to understand the code and how it works.

So we can identify three use cases:

  1. Running mode, when the application is executed: the logging is enabled and configured as required by the application
  2. Test execution mode, when the automated tests are executed all together: the logging messages should be disabled

  3. Test creation mode, when we are creating new tests: the logging messages are useful but it would be nice to have them in the console

Let see an example based on Maven and SLF4J, the popular logging facade.

Complete project can be found here.

Typical SLF4J configuration in the project pom.xml is the following:

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
            <scope>runtime</scope>
        </dependency>

the slf4-api dependency is the main SLF4J library while the second one, slf4j-log4j12, is referencing one of the possible logging engines (LOG4J) that can work below the SLF4J.

This is the running mode configuration. In this example, the project resources will contain a LOG4J properties file which dictates what, how and where LOG4J should log.

The same situation happens when we are using a different logging engine like java.util.logging (JDK) and Logback. See Slf4J manual for more details.

In the Test execution mode we do not want logging so we can simply add the following scope test dependency

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-nop</artifactId>
            <version>${slf4j.version}</version>
            <scope>test</scope>
        </dependency>

The NOP Logger (slf4j-nop) simply discards all logging.

Important: the dependencies order in the pom.xml is significant. Put the slf4j-nop dependency just after the slf4-apidependency so it will be used during the tests even if there is another logging engine in the dependency.

When there are multiple logging engines dependencies in the pom.xml, SLF4J will show anyway a message like the following:

    SLF4J: Class path contains multiple SLF4J bindings.
    SLF4J: Found binding in [jar:file:.m2/repository/org/slf4j/slf4j-nop/1.7.12/slf4j-nop-1.7.12.jar!/org/slf4j/impl/StaticLoggerBinder.class]
    SLF4J: Found binding in [jar:file:.m2/repository/org/slf4j/slf4j-log4j12/1.7.12/slf4j-log4j12-1.7.12.jar!/org/slf4j/impl/StaticLoggerBinder.class]
    SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
    SLF4J: Actual binding is of type [org.slf4j.helpers.NOPLoggerFactory]

To summarize, a complete configuration example is the following:

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-nop</artifactId>
            <version>${slf4j.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
            <scope>runtime</scope>
        </dependency>

Again, the last dependency should be adjusted to match the desired logging engine.

As I mentioned before, logging can be useful when creating new tests. In this case (Test creation mode), we can temporarily replace the slf4j-nop with the slf4j-simple dependnecy which enables the SLF4J Simple logger.

The logging messages will now be shown in the console window during tests execution as System.err messages. No configuration file is needed.

By default, the Simple logger does not log DEBUG messages. Standard logging level is INFO.

You can customize the Simple logger behaviour using system variables documented here.

A smart way to define Simple logger configuration is using the [Surefire plugin configuration section] (http://maven.apache.org/surefire/maven-surefire-plugin/examples/system-properties.html):

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
                <configuration>
                    <systemPropertyVariables>
                        <org.slf4j.simpleLogger.defaultLogLevel>DEBUG</org.slf4j.simpleLogger.defaultLogLevel>
                        <org.slf4j.simpleLogger.showDateTime>true</org.slf4j.simpleLogger.showDateTime>
                    </systemPropertyVariables>
                </configuration>
            </plugin>
        </plugins>
    </build>

In the systemPropertyVariables section we can create tags with the Simple logger variable name. In the above example, DEBUG and timestamp logging are enabled.