Awesome AssertJ: Using A Custom Representation For Objects
The assertion error messages from AssertJ will use the toString()
method of an object to give more insight about why the assertion could have failed. If an object doesn’t override the toString()
method the default implementation is Object#toString()
. The default implementation will print out the class name and an hexadecimal value of hashCode
separated by a @
. This doesn’t give much information about the actual object. For classes we have control over we can always implement a toString()
method, but for third party classes we may not be able to do that. In order to customize how an object is represented in assertion error messages, AssertJ allows us to provide a custom representation class for an object. The custom representation class must implement the org.assertj.core.presentation.Representation
interface. The interface has one method String toStringOf(Object)
that should return a String representation of the object. If we want to keep the default behavior for other classes and only override it for our own class, we can extend the StandardRepresentation
class and override the method String fallbackToStringOf(Object)
.
In the following example we provide a custom representation for the User
class that will print the username
property in the error messages instead of the default toString()
implementation:
package mrhaki;
import org.assertj.core.api.Assertions;
import org.assertj.core.presentation.StandardRepresentation;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class CustomRepresentation {
// Helper class used in tests.
private static class User {
private final String username;
private User(String username) {
this.username = username;
}
}
// Custom representation for the User class.
// Here we can write how we want to represent the User class in
// assertion error messages.
private static class UserRepresentation extends StandardRepresentation {
/**
* The User class doesn't have a toString implementation, so we write
* custom code so in assertion error message our object is nicely formatted.
*
* @param object the object to represent (never {@code null}
* @return Custom string representation of User or standard representation for other objects.
*/
@Override
protected String fallbackToStringOf(Object object) {
if (object instanceof User user) {
return "User(username=%s)".formatted(user.username);
}
return super.fallbackToStringOf(object);
}
}
@Test
void assertUserObject() {
final User mrhaki = new User("mrhaki");
try {
// assert will fail and the assertion error will contain
// a default representation of the objects.
assertThat(mrhaki).isEqualTo(new User("mrhaki42"));
} catch (AssertionError e) {
// expected: mrhaki.CustomRepresentation$User@126253fd
// but was: mrhaki.CustomRepresentation$User@7eecb5b8
assertThat(e).hasMessageContaining("expected: mrhaki.CustomRepresentation$User@")
.hasMessageContaining("but was: mrhaki.CustomRepresentation$User@");
}
}
@Test
void asserUserObjectWithCustomRepresentation() {
final User mrhaki = new User("mrhaki");
try {
// assert will fail and the assertion error will contain
// a custom representation of the objects only for this assert.
assertThat(mrhaki).withRepresentation(new UserRepresentation()).isEqualTo(new User("mrhaki42"));
} catch (AssertionError e) {
// expected: User(username=mrhaki42)
// but was: User(username=mrhaki)
assertThat(e).hasMessageContaining("expected: User(username=mrhaki42)")
.hasMessageContaining("but was: User(username=mrhaki)");
}
}
@Test
void asserUserObjectWithCustomRepresentation2() {
final User mrhaki = new User("mrhaki");
try {
// Set custom representation for User objects for all asserts.
// This can also be defined at class level.
Assertions.useRepresentation(new UserRepresentation());
// assert will fail and the assertion error will contain
// a custom representation of the objects.
assertThat(mrhaki).isEqualTo(new User("mrhaki42"));
} catch (AssertionError e) {
// expected: User(username=mrhaki42)
// but was: User(username=mrhaki)
assertThat(e).hasMessageContaining("expected: User(username=mrhaki42)")
.hasMessageContaining("but was: User(username=mrhaki)");
} finally {
// Reset custom representation.
Assertions.useDefaultRepresentation();
}
}
@Test
void asserUserObjectWithCustomRepresentationSAM() {
final User mrhaki = new User("mrhaki");
try {
// assert will fail and the assertion error will contain
// a custom representation of the objects for this assert only.
assertThat(mrhaki).withRepresentation(new UserRepresentation()).isEqualTo(new User("mrhaki42"));
} catch (AssertionError e) {
// expected: User(username=mrhaki42)
// but was: User(username=mrhaki)
assertThat(e).hasMessageContaining("expected: User(username=mrhaki42)")
.hasMessageContaining("but was: User(username=mrhaki)");
}
}
@Test
void asserUserObjectWithCustomRepresentationSAM() {
final User mrhaki = new User("mrhaki");
try {
// assert will fail and the assertion error will contain
// a custom representation of the objects implemented
// with the Representation interface as single abstract method (SAM).
assertThat(mrhaki).withRepresentation(obj -> {
if (obj instanceof User user) {
return "[User:username=%s]".formatted(user.username);
} else {
return Objects.toIdentityString(obj);
}
}).isEqualTo(new User("mrhaki42"));
} catch (AssertionError e) {
// expected: [User:username=mrhaki42]
// but was: [User:username=mrhaki]
assertThat(e).hasMessageContaining("expected: [User:username=mrhaki42]")
.hasMessageContaining("but was: [User:username=mrhaki]");
}
}
}
Written with AssertJ 3.24.2