开发者

Assert that two java beans are equivalent

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".


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));
}


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:

  • Is there a Java reflection utility to do a deep comparison of two objects?
  • NUnit - Assert to check all properties are equal?


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).


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"))));
}


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).


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));


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


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.


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).


(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.


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜