Writing tests
Writing pojo-methods tests was never so easy. Using POJO-TESTER you just have to declare what class or classes you want to test and pass it to magic pojo-assertions. That's all!
Features
- Test
equalsandhashCode- all branches - Test
gettersandsetters - Test
toString - Test
constructor- including private and non-default (with arguments) - Custom fields value changer
- Custom class creator (using your constructor and parameters)
- Test whole package at once
- Include / exclude fields from testing
Known limitations
- Custom class creator (using your constructor and parameters) does not work with abstract classes
Basic pojo test
Basic tests for Pojo class
The simplest pojo test may look like this:
import static pl.pojo.tester.api.assertion.Assertions.assertPojoMethodsFor;
@Test
public void Should_Pass_All_Pojo_Tests() {
// given
final Class<?> classUnderTest = Pojo.class;
// when
// then
assertPojoMethodsFor(classUnderTest).areWellImplemented();
}
It will test a Pojo class against equals, hashCode, toString, getters and setters which is a default test.
If your pojo-methods are well implemented the test will pass. Otherwise exception will be thrown.
Testing with AssertJ catchThrowable()
If you would rather have strict given-when-then convention, you can use AssertJ and test may look a little bit better.
import static pl.pojo.tester.api.assertion.Assertions.assertPojoMethodsFor;
@Test
public void Should_Pass_All_Pojo_Tests() {
// given
final Class<?> classUnderTest = Pojo.class;
// when
final Throwable result = Assertions.catchThrowable(() -> assertPojoMethodsFor(classUnderTest).areWellImplemented());
// then
assertThat(result).isNull();
}
But remember, with this solution you will not get precise exception message and you may not know why your pojo-methods are not well implemented.
Testing by class name
If your class is not public, you cannot access it. Solution for this problem is testing classes via their names:
import static pl.pojo.tester.api.assertion.Assertions.assertPojoMethodsFor;
@Test
public void Should_Pass_All_Pojo_Tests_When_Testing_By_Name() {
// given
final String qualifiedClassName = "org.pojo.playground.Pojo";
// when
// then
assertPojoMethodsFor(qualifiedClassName).areWellImplemented();
}
When testing by class name you need to pass fully qualified class name.
Testing with ClassAndFieldPredicatePair
You can pair classes and fields that should be tested in a given class in ClassAndFieldPredicatePair. This objects is just a facilitation to you:
import pl.pojo.tester.api.ClassAndFieldPredicatePair;
import static pl.pojo.tester.api.assertion.Assertions.assertPojoMethodsFor;
@Test
public void Should_Pass_All_Pojo_Tests_Using_ClassAndFieldPredicatePair() {
// given
final String qualifiedClassName = "org.pojo.playground.Pojo";
final ClassAndFieldPredicatePair classAndFieldPredicatePair = new ClassAndFieldPredicatePair(qualifiedClassName, FieldPredicate.include("a", "b"));
// when
// then
assertPojoMethodsFor(classAndFieldPredicatePair).areWellImplemented();
}
The code above tests pojo-methods in the org.pojo.playground.Pojo class only for fields a and b.
Changing nested fields
By default Assertions::assertPojoMethodsFor performs tests on a given object with changed field values. It uses field value changers to do that (see fields values changer). When it encounters a field that cannot be changed (e.g. CustomPojo type), it will create a new instance of that type and not perform any changes in this instance. If you want POJO-TESTER to recursively change values of such a field, you have to pass all classes with their field predicates.
For classes:
class Pojo {
private CustomPojo customPojo;
}
class CustomPojo {
private int a;
}
you have to define test as follows:
@Test
public void Should_Pass_All_Pojo_Tests_Changing_Fields_Recursively() {
// given
final ClassAndFieldPredicatePair baseClass = new ClassAndFieldPredicatePair(Pojo.class, "customPojo");
final ClassAndFieldPredicatePair fieldClasses = new ClassAndFieldPredicatePair(CustomPojo.class, "a");
// when
// then
assertPojoMethodsFor(baseClass, fieldClasses).areWellImplemented();
}
Above test means:
Dear
POJO-TESTER, when you create different instances of classPojo, include fieldcustomPojo, but have in mind that thisCustomPojohas fielda. You should generate two instances ofCustomPojo- each with different value of theafield, becausePojo::equalsmethod implementations containscustomPojo.
Choose kind of tests
There is no need for testing pojo-methods in a class that don't implemented them.
You can choose which testers you want to run via pl.pojo.tester.api.assertion.AbstractAssetion::testing method.
Running testers
import pl.pojo.tester.api.assertion.Method;
import static pl.pojo.tester.api.assertion.Assertions.assertPojoMethodsFor;
@Test
public void Should_Pass_All_Pojo_Tests_Using_All_Testers() {
// given
final Class<?> classUnderTest = Pojo.class;
// when
// then
assertPojoMethodsFor(classUnderTest).testing(Method.GETTER, Method.SETTER, Method.TO_STRING)
.testing(Method.EQUALS)
.testing(Method.HASH_CODE)
.testing(Method.CONSTRUCTOR)
.areWellImplemented();
}
Set fields for testing
Next step is excluding or including fields which should be tested. By default all the fields are tested.
You can include or exclude fields using pl.pojo.tester.api.FieldPredicate which creates Java 8 Predicate that accepts given field names.
Include all fields (default behavior)
import static pl.pojo.tester.api.FieldPredicate.includeAllFields;
import static pl.pojo.tester.api.assertion.Assertions.assertPojoMethodsFor;
@Test
public void Should_Pass_All_Pojo_Tests_Including_All_Fields() {
// given
final Class<?> classUnderTest = Pojo.class;
// when
// then
assertPojoMethodsFor(classUnderTest, includeAllFields(classUnderTest)).areWellImplemented();
}
Include specified fields
import static pl.pojo.tester.api.FieldPredicate.include;
import static pl.pojo.tester.api.assertion.Assertions.assertPojoMethodsFor;
@Test
public void Should_Pass_All_Pojo_Tests_Including_Specified_Fields() {
// given
final Class<?> classUnderTest = Pojo.class;
// when
// then
assertPojoMethodsFor(classUnderTest, include("field1", "field2")).areWellImplemented();
}
Exclude spcified fields
import static pl.pojo.tester.api.FieldPredicate.exclude;
import static pl.pojo.tester.api.assertion.Assertions.assertPojoMethodsFor;
@Test
public void Should_Pass_All_Pojo_Tests_Excluding_Specified_Fields() {
// given
final Class<?> classUnderTest = Pojo.class;
// when
// then
assertPojoMethodsFor(classUnderTest, exclude("field1", "field2")).areWellImplemented();
}
Remember. Always prefer excluding over including as this will make your pojo-tests less prone to bugs. For example, if you add a new field, but forget to implement equals method, POJO-TESTER will catch that. But if you chose to use including predicate, then you probably also forgot to include that field in your tests.
Configure field value changer
POJO-TESTERS uses fields values changers to change field value e.g. when creating different instances.
You can change default fields values changer via pl.pojo.tester.api.assertion.AbstractAssetion::using method as shown below.
@Test
public void Should_Pass_All_Pojo_Tests_Using_Custom_Fields_Values_Changer() {
// given
final Class<?> classUnderTest = Pojo.class;
final CustomFieldsValuesChanger customFieldsValuesChanger = new CustomFieldsValuesChanger();
// when
// then
assertPojoMethodsFor(classUnderTest).using(customFieldsValuesChanger)
.areWellImplemented();
}
Define custom fields values changer
To define your own fields values changer you have to extend pl.pojo.tester.internal.field.AbstractFieldValueChanger class.
AbstractFieldValueChanger defines two methods that you have to override:
boolean canChange(final Class<?> type)- this methods should perform compatibility checks e.g. if class is equal to your changer typeT. If you decide that value cannot be changed, no further steps are taken. MethodsareDifferentValuesandincreaseValueare not invoked.T increaseValue(T value, final Class<?> type)- this method should change givenvalueand return new one.typeis given as little help, when your field type is e.g. interface and the value is its implementation.
Custom fields values changer may look like this:
import pl.pojo.tester.internal.field.AbstractFieldValueChanger;
public class CustomFieldsValuesChanger extends AbstractFieldValueChanger<String> {
@Override
protected boolean canChange(final Class<?> type) {
return type.equals(String.class);
}
@Override
protected String increaseValue(final String value, final Class<?> type) {
return value + "++increased";
}
}
Attaching custom fields values changer
Fields values changer uses chain of responsibility pattern which allows you to register new fields values changer to default one.
import pl.pojo.tester.internal.field.AbstractFieldValueChanger;
import pl.pojo.tester.internal.field.DefaultFieldValueChanger;
final AbstractFieldValueChanger valueChanger = DefaultFieldValueChanger.INSTANCE.attachNext(customFieldsValuesChanger)
.attachNext(customFieldsValuesChanger)
.attachNext(customFieldsValuesChanger);
Default fields values changer
Default field value changer (DefaultFieldValueChanger.INSTANCE) is a composition of listed changers:
Collections
ArrayListValueChangerDequeValueChangerHashSetValueChangerLinkedHashSetValueChangerLinkedListValueChangerListValueChangerQueueValueChangerSetValueChangerSortedSetValueChangerStackValueChangerTreeSetValueChangerVectorValueChangerIterableValueChangerIteratorValueChangerHashMapValueChangerHashtableValueChangerLinkedHashMapValueChangerMapValueChangerSortedMapValueChangerTreeMapValueChangerArrayValueChangerStreamValueChanger
Date and time
DateFieldValueChangerInstantFieldValueChangerLocalDateFieldValueChangerLocalDateTimeFieldValueChangerLocalTimeFieldValueChangerSqlDateFieldValueChangerZonedDateTimeFieldValueChanger
Math
BigDecimalValueChangerBigIntegerValueChanger
Primitives and wrappers
BooleanValueChangerByteValueChangerCharacterValueChangerDoubleValueChangerFloatValueChangerIntegerValueChangerLongValueChangerShortValueChanger
Others
EnumValueChangerStringValueChangerUUIDValueChanger
Create class using selected constructor
Sometimes you want to choose which constructor is used to instantiate your class or what parameters are passed. Common example is when constructor validates parameters and throws exceptions.
To indicate what constructor to choose, POJO-TESTER needs to know three things:
- a class, which constructor will be chosen
- constructor's parameters types
- constructor's parameters
And again, defining this in POJO-TESTER is a piece of cake:
import static pl.pojo.tester.api.assertion.Assertions.assertPojoMethodsFor;
@Test
public void Should_Pass_All_Pojo_Tests() {
// given
final String qualifiedClassName = "org.pojo.playground.Pojo";
final Object[] constructorParameters = {1, 2.0, new Object()};
final Class[] constructorParameterTypes = {int.class, double.class, Object.class};
// when
// then
assertPojoMethodsFor(qualifiedClassName).create(qualifiedClassName, constructorParameters, constructorParameterTypes)
.areWellImplemented();
}
Here POJO-TESTER provides additional class, which groups constructor's parameters types and constructor parameters:
import pl.pojo.tester.api.ConstructorParameters;
import static pl.pojo.tester.api.assertion.Assertions.assertPojoMethodsFor;
@Test
public void Should_Pass_All_Pojo_Tests() {
// given
final String qualifiedClassName = "org.pojo.playground.Pojo";
final Object[] parameters = {1, 2.0, new Object()};
final Class[] parameterTypes = {int.class, double.class, Object.class};
final ConstructorParameters constructorParameters = new ConstructorParameters(parameters, parameterTypes);
// when
// then
assertPojoMethodsFor(qualifiedClassName).create(qualifiedClassName, constructorParameters)
.areWellImplemented();
}
Bulk pojos testing
Sometimes you want to test all pojos in one test, e.g. testing toString method. POJO-TESTER has feature for testing multiple classes. In order to do that, you have to use Assertions::assertPojoMethodsForAll instead of Assertions::assertPojoMethodsFor method:
import static pl.pojo.tester.api.assertion.Assertions.assertPojoMethodsForAll;
@Test
public void Should_Pass_All_Pojo_Tests_For_All_Classes() {
// given
final Class<Pojo> classUnderTest = Pojo.class;
// when
// then
assertPojoMethodsForAll(classUnderTest, classUnderTest, classUnderTest, classUnderTest).areWellImplemented();
}
Method assertPojoMethodsForAll works a little bit differently than assertPojoMethodsFor.
This method test all classes. If it encounters field of type from given classes, it will create an instance of that class and change its value recursively.
Imagine you are testing two classes, A and B. Class A has a field of type B. When you pass those classes to the POJO-TESTER, it will create instance of B class, change its value generating different objects, and finally will set all those objects into class A.
If testing time grows...
... then you can use switch which will generate less objects to tests. It may cause lower code coverage.
Usage:
assertPojoMethodsFor(classUnderTest).quickly()
.areWellImplemented();
By default it will perform full tests - with much more generated objects than using AbstractAssertion#quickly method.
For more details see #201
Debugging
pojo-tester is not a perfect library. Sometimes you have to debug tests to see what happened.
If you think that pojo-tester has a bug, just switch logging level to DEBUG and investigate.
Switching to debug level
E.g. log4j.properties file:
# Root logger option
log4j.rootLogger=DEBUG, stdout
# Direct log messages to stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p - %m%n
Place it in test resources directory:

Log analyses
List of field value changers:
(...) Attaching pl.pojo.tester.internal.field.primitive.ByteValueChangerto pl.pojo.tester.internal.field.primitive.BooleanValueChanger
(...) Attaching pl.pojo.tester.internal.field.primitive.CharacterValueChangerto pl.pojo.tester.internal.field.primitive.ByteValueChanger
(...) Attaching pl.pojo.tester.internal.field.primitive.DoubleValueChangerto pl.pojo.tester.internal.field.primitive.CharacterValueChanger
(...) Attaching pl.pojo.tester.internal.field.primitive.IntegerValueChangerto pl.pojo.tester.internal.field.primitive.DoubleValueChanger
(...) Attaching pl.pojo.tester.internal.field.primitive.LongValueChangerto pl.pojo.tester.internal.field.primitive.IntegerValueChanger
Testers that were used:
(...) Testers: [EqualsTester, ToStringTester, GetterTester, HashCodeTester, SetterTester, ConstructorTester]
Changing field value:
(...) Changing value of type class java.lang.String from 'www.pojo.pl' to 'www.pojo.pl++increased' (pl.pojo.tester.internal.field.StringValueChanger@21a947fe)
(...) Changing value of type int from '1' to '2' (pl.pojo.tester.internal.field.primitive.IntegerValueChanger@69a10787)
Generating objects:
(..1) Classes: [pl.pojo.tester.model.Pojo2(a,b,c,pojo,pojo3), pl.pojo.tester.model.Pojo(a,b,c), pl.pojo.tester.model.Pojo3(a,b,c,pojo)]
(..2) Start of generating different objects for base class pl.pojo.tester.model.Pojo2(a,b,c,pojo,pojo3). Base object is Pojo2(a=0, b=0, c=0, pojo=null, pojo3=null) -- others will be cloned from this one
(..3) Caching 8 different objects for class pl.pojo.tester.model.Pojo in dejaVu cache
(..4) Reusing 8 objects from 'dejaVu' cache for class pl.pojo.tester.model.Pojo
(...) Reusing 8 objects from 'dejaVu' cache for class pl.pojo.tester.model.Pojo
(...) Reusing 8 objects from 'dejaVu' cache for class pl.pojo.tester.model.Pojo
(...) Reusing 8 objects from 'dejaVu' cache for class pl.pojo.tester.model.Pojo
(...) Reusing 8 objects from 'dejaVu' cache for class pl.pojo.tester.model.Pojo
(...) Reusing 8 objects from 'dejaVu' cache for class pl.pojo.tester.model.Pojo
(...) Reusing 8 objects from 'dejaVu' cache for class pl.pojo.tester.model.Pojo
(..5) Start of generating different objects for base class pl.pojo.tester.model.Pojo3(a,b,c,pojo). Base object is Pojo3(a=0, b=0, c=0, pojo=null) -- others will be cloned from this one
(...) Reusing 8 objects from 'dejaVu' cache for class pl.pojo.tester.model.Pojo
(...) Reusing 8 objects from 'dejaVu' cache for class pl.pojo.tester.model.Pojo
(...) Reusing 8 objects from 'dejaVu' cache for class pl.pojo.tester.model.Pojo
(...) Reusing 8 objects from 'dejaVu' cache for class pl.pojo.tester.model.Pojo
(...) Reusing 8 objects from 'dejaVu' cache for class pl.pojo.tester.model.Pojo
(...) Reusing 8 objects from 'dejaVu' cache for class pl.pojo.tester.model.Pojo
(...) Reusing 8 objects from 'dejaVu' cache for class pl.pojo.tester.model.Pojo
(...) Reusing 8 objects from 'dejaVu' cache for class pl.pojo.tester.model.Pojo
(..6) End of generating different objects (size=72) for base class pl.pojo.tester.model.Pojo3(a,b,c,pojo)
(...) Caching 72 different objects for class pl.pojo.tester.model.Pojo3 in dejaVu cache
(...) Reusing 72 objects from 'dejaVu' cache for class pl.pojo.tester.model.Pojo3
(...) Reusing 72 objects from 'dejaVu' cache for class pl.pojo.tester.model.Pojo3
(..7) End of generating different objects (size=5256) for base class pl.pojo.tester.model.Pojo2(a,b,c,pojo,pojo3
1print classes that are being tested2start generating different objects for given class3some objects are cached in case of need for generating them later4and some of them are reused5generating nested object (see that tabulator is used - this means second level in hierarchy)6end of generating nested object7end of generating base object