Assert that two java beans are equivalent
Asked Answered
A

13

14

This question is close, but still not what I want. I'd like to assert in a generic way that two bean objects are equivalent. In case they are not, I'd like a detailed error message explaining the difference instead of a boolean "equal" or "not equal".

Anybody answered 16/11, 2009 at 9:27 Comment(4)
you might find this blog entry enlightening blogs.atlassian.com/developer/2009/06/…Mazer
@Chii: Any reason why you posted this as a comment? that's a good answer!Dispirit
Here's a meta-problem: you're confusing "identical", "equal" and "equivalent", just like everyone else.Bubble
@Bubble - you're right: equal and equivalent are not identical ;)Nonalignment
M
13

I recommend you use unitils library:

http://www.unitils.org/tutorial-reflectionassert.html

public class User {

    private long id;
    private String first;
    private String last;

    public User(long id, String first, String last) {
        this.id = id;
        this.first = first;
        this.last = last;
    }
}
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertReflectionEquals(user1, user2);

See also:

Mal answered 12/2, 2013 at 7:23 Comment(2)
Great answer!, I didn't know that, and writing unit using assertEquals for each property is so much painGelatin
I have to say this library is super cool. And if the test fails, it reports exactly which fields caused the failure and what their values were. Good job man.Buller
S
19
import static org.hamcrest.beans.SamePropertyValuesAs.samePropertyValuesAs;
import static org.junit.Assert.assertThat;

@Test
public void beansAreTheSame(){
    MyDomianClass bean1 = new MyDomainClass();
    MyDomianClass bean2 = new MyDomainClass();
    //TODO - some more test logic

    assertThat(bean1, samePropertyValuesAs(bean2));
}
Swing answered 23/3, 2015 at 8:19 Comment(3)
This needs more visibility!Sewel
Won't compare deep objectsPaludal
@AndreyRodionov: agreed - that sucks. Still better than nothing, though.Weisshorn
M
13

I recommend you use unitils library:

http://www.unitils.org/tutorial-reflectionassert.html

public class User {

    private long id;
    private String first;
    private String last;

    public User(long id, String first, String last) {
        this.id = id;
        this.first = first;
        this.last = last;
    }
}
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertReflectionEquals(user1, user2);

See also:

Mal answered 12/2, 2013 at 7:23 Comment(2)
Great answer!, I didn't know that, and writing unit using assertEquals for each property is so much painGelatin
I have to say this library is super cool. And if the test fails, it reports exactly which fields caused the failure and what their values were. Good job man.Buller
D
5

You can use Commons Lang's ToStringBuilder to convert both of them into readable strings and then use assertEquals() on both strings.

If you like XML, you can use java.lang.XMLEncoder to turn your bean into XML and then compare the two XML documents.

Personally, I prefer ToStringBuilder since it gives you more control over the formatting and allows you to do things like sorting the elements in a set to avoid false negatives.

I suggest to put each field of the bean in a different line to make it much more simple to compare them (see my blog for details).

Dispirit answered 16/11, 2009 at 9:35 Comment(1)
Great, I didn't know about this. Actually, I need something like this badly. Thanks pal.Thiamine
P
3

You can set all fields like this:

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.beans.HasPropertyWithValue.hasProperty;
import static org.hamcrest.Matchers.is;

@Test
public void test_returnBean(){

    arrange();

    MyBean myBean = act();

    assertThat(myBean, allOf(hasProperty("id",          is(7L)), 
                             hasProperty("name",        is("testName1")),
                             hasProperty("description", is("testDesc1"))));
}
Prognosis answered 9/3, 2016 at 19:9 Comment(0)
N
1

I think, the most generic approach is to reflect the bean members and test them for equality one-by-one. The common lang's EqualsBuilder is a good start and it should be not a big deal, to adapt it (on source level) to your requirements (reporting the differences instead of returning the equals result).

Nonalignment answered 16/11, 2009 at 9:40 Comment(3)
-1 That doesn't give a detailed error message what is different.Dispirit
I used this as well but with a slight different. In order to also get a meaningful error message after using EqualsBuilder. reflectionEquals I do an assert on the two ToStringBuilder.reflectionToString (finally with a fail(...) should the to string be identical actually successed). Eclipse will then highlight the diffrence between the two objects.Wildfire
@Aaron - I know, and that's why I suggested to adapt the EqualsBuilder code.Nonalignment
B
1

For unit testing this can be done with JUnit and Mockito using ReflectionEquals. When implementing in the following manner, it will dump the JSON representations of the objects when any fields are not equal which makes it easy to find the offending difference.

import static org.junit.Assert.assertThat;
import org.mockito.internal.matchers.apachecommons.ReflectionEquals;

assertThat("Validating field equivalence of objects", expectedObjectValues, new ReflectionEquals(actualObjectValues));
Berton answered 20/5, 2015 at 1:47 Comment(0)
W
1

If you need to check partial equivalent or some sophisticated conditions on tested bean (e.g. some list property contains 4 elements) you can check the https://github.com/hortonolite/hamcrestBeanMatcherApt project. It is a Java APT processor what generate a matcher builder class for bean with Hamcrest Matcher.

For example, for beans

@Value
@Builder
public class TestBean02 {
    String stringValue;
    List<String> listValue;
    TestBean03 anotherBean;
}
@Value
@Builder
public class TestBean03 {
    int intValue;
    int[] intArrayValue;
}

It will generate TestBean02Matcher and you can create matcher for any property combinations.

TestBean03 anotherBean= TestBean03.builder()
                          .intValue(10)
                          .intArrayValue(new int[] { 2, 4, 6, 8, 0 })
                          .build();
TestBean02 bean = TestBean02.builder()
                     .stringValue("value01")
                     .listValue(List.of("a", "b", "c"))
                     .anotherBean(anotherBean)
                     .build();
assertThat(bean, testBean02Matcher()
                        .withListValue(contains("a", "b", "c"))
                        .withAnotherBean(testBean03Matcher()
                                .withIntArrayValueSize(5)));
Whoop answered 30/8, 2023 at 17:3 Comment(0)
F
0

Since you didn't like the answers in the question you referenced, why not just have a toXml method in each bean, turn them into an xml file and then use xmlUnit to compare.

You can get more info on comparing xml files here:

Best way to compare 2 XML documents in Java

Forceful answered 16/11, 2009 at 9:32 Comment(1)
My only concern is that some properties may need to be excluded, by using toXml() it forces that he knows what is being compared, otherwise your comment would be more correct.Forceful
O
0

You're not really asserting equality, more doing a "diff". Clearly, the meaning of "same" depends upon particular logic for each type, and the representation of the difference also may vary. One major difference between this requirment and a conventional equals() is that usually equals() will stop as soon as the first difference is seen, you will want to carry on and compare every field.

I would look at reusing some of the equals() patterns, but I suspect you'll need to write your own code.

Olio answered 16/11, 2009 at 9:35 Comment(0)
C
0

I am assuming here that both beans are of the same type, in which case only the member variable values will differ across bean instances.

Define an util class (public static final with private ctor) called, say, BeanAssertEquals. Use Java reflection to obtain the value of each member variable in each bean. Then do an equals() between values for the same member variable in different beans. If an equality fails, mention the field name.

Note: member variables are usually private, so you would need to use reflection to temporarily change the accessibility of private members.

Additionally, depending how fine-grained you want the assertion to work, you should consider the following:

  1. Equality of member variables not in the bean class but all superclasses.

  2. Equality of elements in arrays, in case a member variable is of type array.

  3. For two values of a given member across beans, you might consider doing BeanAssertEquals.assertEquals(value1, value2) instead of value1.equals(value2).

Cestus answered 16/11, 2009 at 9:44 Comment(0)
W
0

(to build on my comment to Andreas_D above)

/** Asserts two objects are equals using a reflective equals.
 * 
 * @param message The message to display.
 * @param expected The expected result.
 * @param actual The actual result
 */
public static void assertReflectiveEquals(final String message, 
        final Object expected, final Object actual) {
    if (!EqualsBuilder.reflectionEquals(expected, actual)) {
        assertEquals(message, 
                reflectionToString(expected, ToStringStyle.SHORT_PREFIX_STYLE), 
                reflectionToString(actual, ToStringStyle.SHORT_PREFIX_STYLE));
        fail(message + "expected: <" + expected + ">  actual: <" + actual + ">");
    }
}

This is what I use, and I believe it meets all basic requirements. By doing the assert on the reflective ToString then Eclipse will highlight the difference.

While Hamcrest can offer a much nicer message, this does involve a good deal less code.

Wildfire answered 16/11, 2009 at 10:43 Comment(0)
S
-1

The first quesion I'd have to ask if is, do you want to do 'deep' equals on the Bean? does it have child beans that need to be tested? You can override the equals method, but this only returns a boolean, so you could create a 'comparator' and that could throw an exception with a message about what was not equal.

In the following examples, I've listed a few ways to implement the equals method.

if you want to check if they are the same object instance, then the normal equals method from Object will tell you.

    objectA.equals(objectB);

if you want to write a customer equals method to check that all the member varibles of an object make them equal then you can override the equals method like this...

  /**
     * Method to check the following...
     * <br>
     * <ul>
     *    <li>getTitle</li>
     *    <li>getInitials</li>
     *    <li>getForename</li>
     *    <li>getSurname</li>
     *    <li>getSurnamePrefix</li>
     * </ul>
     *
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj)
    {
      if (   (!compare(((ICustomer) obj).getTitle(), this.getTitle()))
          || (!compare(((ICustomer) obj).getInitials(), this.getInitials()))
          || (!compare(((ICustomer) obj).getForename(), this.getForename()))
          || (!compare(((ICustomer) obj).getSurname(), this.getSurname()))
          || (!compare(((ICustomer) obj).getSurnamePrefix(), this.getSurnamePrefix()))
          || (!compare(((ICustomer) obj).getSalutation(), this.getSalutation()))  ){
          return false;
      }
      return true;
    }

The last option is to use java reflection to check all the member varibles in the equals method. This is great if you really want to check every member varible via its bean get/set method. It wont (I dont think) allow you to check private memeber varibles when testing of the two objects are the same. (not if your object model has a circular dependancy, dont do this, it will never return)

NOTE: this is not my code, it comes from...

Java Reflection equals public static boolean equals(Object bean1, Object bean2) { // Handle the trivial cases if (bean1 == bean2) return true;

if (bean1 == null)
return false;

if (bean2 == null)
return false;

// Get the class of one of the parameters
Class clazz = bean1.getClass();

// Make sure bean1 and bean2 are the same class
if (!clazz.equals(bean2.getClass()))
{
return false;
}

// Iterate through each field looking for differences
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++)
{
// setAccessible is great (encapsulation
// purists will disagree), setting to true
// allows reflection to have access to
// private members.
fields[i].setAccessible(true);
try
{
Object value1 = fields[i].get(bean1);
Object value2 = fields[i].get(bean2);

if ((value1 == null && value2 != null) ||
(value1 != null && value2 == null))
{
return false;
}

if (value1 != null &&
value2 != null &&
!value1.equals(value2))
{
return false;
}
}
catch (IllegalArgumentException e)
{
e.printStackTrace();
}
catch (IllegalAccessException e)
{
e.printStackTrace();
}
}

return true;

The one thing that this does not do it to tell you the reason for the difference, but that could be done via message to Log4J when you find a section that is not equal.

Seem answered 16/11, 2009 at 9:35 Comment(2)
-1 This just says "there is a difference" but gives you no clue what the difference might be.Dispirit
It shouldn't be necessary to even have logs while unit testing. The failed tests should tell you all you need to know.Domella
S
-1

The xtendbeans library could be of interest in this context:

AssertBeans.assertEqualBeans(expectedBean, actualBean);

This produces a JUnit ComparisonFailure à la:

expected: 
    new Person => [
        firstName = 'Homer'
        lastName = 'Simpson'
        address = new Address => [
          street = '742 Evergreen Terrace'
          city = 'SpringField'
       ]
  ]
but was:
    new Person => [
        firstName = 'Marge'
        lastName = 'Simpson'
        address = new Address => [
          street = '742 Evergreen Terrace Road'
          city = 'SpringField'
       ]
  ]

You could also use it just to get the textual representation for other purposes:

String beanAsLiteralText = new XtendBeanGenerator().getExpression(yourBean)

With this library you can use the above syntactically valid object initialization code fragment to copy/paste it into a (Xtend) source class for the expectedBean, but you don't not have to, it can perfectly well be used without Xtend as well.

Sememe answered 30/8, 2016 at 8:53 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.