How do I assert equality on two classes without an equals method?
Asked Answered
V

24

189

Say I have a class with no equals() method, to which do not have the source. I want to assert equality on two instances of that class.

I can do multiple asserts:

assertEquals(obj1.getFieldA(), obj2.getFieldA());
assertEquals(obj1.getFieldB(), obj2.getFieldB());
assertEquals(obj1.getFieldC(), obj2.getFieldC());
...

I don't like this solution because I don't get the full equality picture if an early assert fails.

I can manually compare on my own and track the result:

String errorStr = "";
if(!obj1.getFieldA().equals(obj2.getFieldA())) {
    errorStr += "expected: " + obj1.getFieldA() + ", actual: " + obj2.getFieldA() + "\n";
}
if(!obj1.getFieldB().equals(obj2.getFieldB())) {
    errorStr += "expected: " + obj1.getFieldB() + ", actual: " + obj2.getFieldB() + "\n";
}
...
assertEquals("", errorStr);

This gives me the full equality picture, but is clunky (and I haven't even accounted for possible null problems). A third option is to use Comparator, but compareTo() will not tell me which fields failed equality.

Is there a better practice to get what I want from the object, without subclassing and overridding equals (ugh)?

Vivl answered 27/8, 2012 at 18:15 Comment(7)
Are you looking for a library that does deep comparison for you? like deep-equals suggested at #1449501?Chelyabinsk
Why do you need to know why the two instances were not equal. Usually, an implementation of equal method only tells whether two instances are equal, and we don't care why the intances are not equal.Sucrase
I want to know what properties are unequal so I can fix them. :)Vivl
All Objects have an equals method, you probably meant no overridden equals method.Hypnotist
The best way I can think of is to use a wrapper class or a subclass and then use it after overriding the equals method..Imre
In my case the class hasn't overriden equals method but toString method, so I am using that.Friesland
I think the questions covers more details, but since you are worried about an earlier assert that might fail, this might be a tip worthy your attention: 'assertAll' always checks all of the assertions that are passed to it, no matter how many fail. If all pass, all is fine - if at least one fails you get a detailed result of all that went wrong (and right for that matter). -> See Nicolai Parlogs Answer on #40797256Tympan
B
137

There is many correct answers here, but I would like to add my version too. This is based on Assertj.

import static org.assertj.core.api.Assertions.assertThat;

public class TestClass {

    public void test() {
        // do the actual test
        assertThat(actualObject)
            .isEqualToComparingFieldByFieldRecursively(expectedObject);
    }
}

UPDATE: In assertj v3.13.2 this method is deprecated as pointed out by Woodz in comment. Current recommendation is

public class TestClass {

    public void test() {
        // do the actual test
        assertThat(actualObject)
            .usingRecursiveComparison()
            .isEqualTo(expectedObject);
    }

}
Beatabeaten answered 27/6, 2017 at 13:41 Comment(1)
In assertj v3.13.2 this method is deprecated and the recommendation is now to use usingRecursiveComparison() with isEqualTo(), such that the line is assertThat(actualObject).usingRecursiveComparison().isEqualTo(expectedObject);Nottingham
A
91

Mockito offers a reflection-matcher:

For latest version of Mockito use:

Assert.assertTrue(new ReflectionEquals(expected, excludeFields).matches(actual));

For older versions use:

Assert.assertThat(actual, new ReflectionEquals(expected, excludeFields));
Amboceptor answered 22/4, 2015 at 9:37 Comment(9)
This class is in package org.mockito.internal.matchers.apachecommons. Mockito docs state: org.mockito.internal -> "Internal classes, not to be used by clients." You will put your project into a risk using this. This can change in any Mockito version. Read here: site.mockito.org/mockito/docs/current/overview-summary.htmlIbadan
Use Mockito.refEq() instead.Pilau
Mockito.refEq() fails when the objects don't have an id set =(Aureole
@Aureole What do you mean by "id"?Fylfot
@PiotrAleksanderChmielowski, sorry, when working with Spring+JPA+Entities, the Entity object may have an id (representing the id field of the database table), so when it is empty (a new object not yet stored on the DB), refEq fails to compare as the hashcode method is unable to compare the objects.Aureole
It works fine, but expected and actual are in wrong order. It should be the other way around.Selfabsorption
I highly doubt this work, at least in current versions. ReflectionEquals is ArgumentMatcher and not matcher. And refEq returning null is even more out of question.Morez
Lists new ArrayList<>() and Arrays.asList() with the same content are considered different by ReflectionEquals.Alaska
Can I make the be considered the same?Alaska
C
59

I generally implement this usecase using org.apache.commons.lang3.builder.EqualsBuilder

Assert.assertTrue(EqualsBuilder.reflectionEquals(expected,actual));
Cardialgia answered 3/3, 2014 at 23:25 Comment(5)
Gradle: androidTestCompile 'org.apache.commons:commons-lang3:3.5'Minefield
You need to add this to your gradle file under "dependencies" when you want to use "org.apache.commons.lang3.builder.EqualsBuilder"Minefield
This doesn't give any hint on what exact fields didn't match actually.Dahomey
@Dahomey I have used the below code to get that Assert.assertEquals(ReflectionToStringBuilder.toString(expected), ReflectionToStringBuilder.toString(actual));Cardialgia
This one requires all of the nodes in the graph implement "equal" and "hashcode", which basically make this method nearly useless. AssertJ's isEqualToComparingFieldByFieldRecursively is the one working perfectly in my case.Virago
A
15

I know it's a bit old, but I hope it helps.

I run into the same problem that you, so, after investigation, I found few similar questions than this one, and, after finding the solution, I'm answering the same in those, since I thought it could to help others.

The most voted answer (not the one picked by the author) of this similar question, is the most suitable solution for you.

Basically, it consist on using the library called Unitils.

This is the use:

User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertReflectionEquals(user1, user2);

Which will pass even if the class User doesn't implement equals(). You can see more examples and a really cool assert called assertLenientEquals in their tutorial.

Adept answered 17/12, 2014 at 17:6 Comment(1)
Unfortunately Unitils seems to have died, see https://mcmap.net/q/137031/-is-unitils-project-alive .Woald
C
13

If you're using hamcrest for your asserts (assertThat) and don't want to pull in additional test libs, then you can use SamePropertyValuesAs.samePropertyValuesAs to assert items that don't have an overridden equals method.

The upside is that you don't have to pull in yet another test framework and it'll give a useful error when the assert fails (expected: field=<value> but was field=<something else>) instead of expected: true but was false if you use something like EqualsBuilder.reflectionEquals().

The downside is that it is a shallow compare and there's no option for excluding fields (like there is in EqualsBuilder), so you'll have to work around nested objects (e.g. remove them and compare them independently).

Best Case:

import static org.hamcrest.beans.SamePropertyValuesAs.samePropertyValuesAs;
...
assertThat(actual, is(samePropertyValuesAs(expected)));

Ugly Case:

import static org.hamcrest.beans.SamePropertyValuesAs.samePropertyValuesAs;
...
SomeClass expected = buildExpected(); 
SomeClass actual = sut.doSomething();

assertThat(actual.getSubObject(), is(samePropertyValuesAs(expected.getSubObject())));    
expected.setSubObject(null);
actual.setSubObject(null);

assertThat(actual, is(samePropertyValuesAs(expected)));

So, pick your poison. Additional framework (e.g. Unitils), unhelpful error (e.g. EqualsBuilder), or shallow compare (hamcrest).

Commotion answered 1/3, 2018 at 3:52 Comment(3)
For work SamePropertyValuesAs you must add in project dependecy hamcrest.org/JavaHamcrest/…Houseline
I like this solution, as it doesn't add a "completely* different additional dependency!Trichromat
Another downside: it uses property (getters) instead of comparing fields.Housekeeping
D
9

You can use Apache commons lang ReflectionToStringBuilder

You can either specify the attributes you want to test one by one, or better, exclude those you don't want:

String s = new ReflectionToStringBuilder(o, ToStringStyle.SHORT_PREFIX_STYLE)
                .setExcludeFieldNames(new String[] { "foo", "bar" }).toString()

You then compare the two strings as normal. For the point about reflection being slow, I assume this is only for testing, so shouldn't be so important.

Dioptase answered 28/8, 2012 at 7:33 Comment(2)
Added benefit of this approach is that you get visual output displaying expected and actual values as strings excluding fields you don't care about.Palaeogene
Is there a way to exclude the address value that is present there which converting to String typeLevity
P
9

Since this question is old, I will suggest another modern approach using JUnit 5.

I don't like this solution because I don't get the full equality picture if an early assert fails.

With JUnit 5, there is a method called Assertions.assertAll() which will allow you to group all assertions in your test together and it will execute each one and output any failed assertions at the end. This means that any assertions that fail first will not stop the execution of latter assertions.

assertAll("Test obj1 with obj2 equality",
    () -> assertEquals(obj1.getFieldA(), obj2.getFieldA()),
    () -> assertEquals(obj1.getFieldB(), obj2.getFieldB()),
    () -> assertEquals(obj1.getFieldC(), obj2.getFieldC()));
Petrochemistry answered 29/1, 2020 at 3:51 Comment(0)
A
4

The library Hamcrest 1.3 Utility Matchers has a special matcher that uses reflection instead of equals.

assertThat(obj1, reflectEquals(obj2));
Antonia answered 7/1, 2015 at 10:48 Comment(0)
D
4

AssertJ assertions can be used to compare the values without #equals method properly overridden, e.g.:

import static org.assertj.core.api.Assertions.assertThat; 

// ...

assertThat(actual)
    .usingRecursiveComparison()
    .isEqualTo(expected);
Dancy answered 9/12, 2019 at 7:42 Comment(0)
M
3

Some of the reflection compare methods are shallow

Another option is to convert the object to a json and compare the strings.

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;    
public static String getJsonString(Object obj) {
 try {
    ObjectMapper objectMapper = new ObjectMapper();
    return bjectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
     } catch (JsonProcessingException e) {
        LOGGER.error("Error parsing log entry", e);
        return null;
    }
}
...
assertEquals(getJsonString(MyexpectedObject), getJsonString(MyActualObject))
Misdirect answered 26/3, 2018 at 21:31 Comment(0)
T
2

Using Shazamcrest, you can do:

assertThat(obj1, sameBeanAs(obj2));
Tulipwood answered 12/9, 2018 at 17:36 Comment(2)
Why use shazamcrest instead of hamcrest ? https://mcmap.net/q/134950/-how-do-i-assert-equality-on-two-classes-without-an-equals-methodCallista
@MasterJoe2 In my tests, reflectEquals returns false when the objects have different references.Tulipwood
A
1

Compare field-by-field:

assertNotNull("Object 1 is null", obj1);
assertNotNull("Object 2 is null", obj2);
assertEquals("Field A differs", obj1.getFieldA(), obj2.getFieldA());
assertEquals("Field B differs", obj1.getFieldB(), obj2.getFieldB());
...
assertEquals("Objects are not equal.", obj1, obj2);
Arillode answered 27/8, 2012 at 18:23 Comment(5)
This is something I don't want to do, because an early assert failure will hide possible failures below.Vivl
Sorry, I missed that part of your post... Why is a "full equality picture" important in a unit-test environment? Either the fields are all equal (test passes), or they are not all equal (test fails).Arillode
I don't want to have to re-run the test to discover if other fields are not equal. I want to know up-front all fields that are unequal so I can address them at once.Vivl
Asserting many fields in one test would not be considered a true 'unit' test. With traditional test-driven development (TDD), you write a small test, and then only enough code to make it pass. Having one assert per field is the correct way to do it, just don't put all the asserts into one test. Create a different test for each field assertion you care about. This will allow you to see all the errors with all the fields with a single run of the suite. If this is hard, it likely means your code isn't modular enough in the first place and can likely be refactored into a cleaner solution.Rapine
This is definitely a valid suggestion, and is the usual way you'd solve multiple asserts in a single test. The only challenge here is, I'd like a holistic view of the object. That is, I'd like to test all fields simultaneously to verify that the object is in a valid state. This is not hard to do when you have an overridden equals() method (which of course I do not have in this example).Vivl
E
1

You can use reflection to "automate" the full equality testing. you can implement the equality "tracking" code you wrote for a single field, then use reflection to run that test on all fields in the object.

Elohim answered 27/8, 2012 at 18:50 Comment(1)
Best approach regarding the underlying principle, but a hint to something read-to-use like Apache Commons would have been better. Nevertheless I give an upvote.Transeunt
T
1

In case you just need flat fields comparison you can use AssertJ

Assertions.assertThat(actual)).isEqualToComparingFieldByField(expected);
Trollope answered 24/5, 2022 at 9:23 Comment(1)
It's deprecated, use assertThat(actualObject).usingRecursiveComparison().isEqualTo(expectedObject);Denitadenitrate
P
0

This is a generic compare method , that compares two objects of a same class for its values of it fields(keep in mind those are accessible by get method)

public static <T> void compare(T a, T b) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    AssertionError error = null;
    Class A = a.getClass();
    Class B = a.getClass();
    for (Method mA : A.getDeclaredMethods()) {
        if (mA.getName().startsWith("get")) {
            Method mB = B.getMethod(mA.getName(),null );
            try {
                Assert.assertEquals("Not Matched = ",mA.invoke(a),mB.invoke(b));
            }catch (AssertionError e){
                if(error==null){
                    error = new AssertionError(e);
                }
                else {
                    error.addSuppressed(e);
                }
            }
        }
    }
    if(error!=null){
        throw error ;
    }
}
Preraphaelite answered 1/2, 2017 at 19:19 Comment(0)
A
0

I stumbled on a very similar case.

I wanted to compare on a test that an object had the same attribute values as another one, but methods like is(), refEq(), etc wouldn't work for reasons like my object having a null value in its id attribute.

So this was the solution I found (well, a coworker found):

import static org.apache.commons.lang.builder.CompareToBuilder.reflectionCompare;

assertThat(reflectionCompare(expectedObject, actualObject, new String[]{"fields","to","be","excluded"}), is(0));

If the value obtained from reflectionCompare is 0, it means they are equal. If it is -1 or 1, they differ on some attribute.

Aureole answered 19/5, 2017 at 16:45 Comment(0)
T
0

In common case with AssertJ you can create custom comparator strategy:

assertThat(frodo).usingComparator(raceComparator).isEqualTo(sam)
assertThat(fellowshipOfTheRing).usingElementComparator(raceComparator).contains(sauron);

Using a custom comparison strategy in assertions

AssertJ examples

Tague answered 2/10, 2017 at 21:39 Comment(0)
H
0

I had the exact same conundrum when unit testing an Android app, and the easiest solution I came up with was simply to use Gson to convert my actual and expected value objects into json and compare them as strings.

String actual = new Gson().toJson( myObj.getValues() );
String expected = new Gson().toJson( new MyValues(true,1) );

assertEquals(expected, actual);

The advantages of this over manually comparing field-by-field is that you compare all your fields, so even if you later on add a new field to your class it will get automatically tested, as compared to if you were using a bunch of assertEquals() on all the fields, which would then need to be updated if you add more fields to your class.

jUnit also displays the strings for you so you can directly see where they differ. Not sure how reliable the field ordering by Gson is though, that could be a potential problem.

Highflier answered 7/4, 2018 at 9:29 Comment(1)
Fields order is not guaranteed by Gson. You might want to JsonParse the strings and compare the JsonElements resulted from parsingLightproof
Z
0

I tried all the answers and nothing really worked for me.

So I've created my own method that compares simple java objects without going deep into nested structures...

Method returns null if all fields match or string containing mismatch details.

Only properties that have a getter method are compared.

How to use

        assertNull(TestUtils.diff(obj1,obj2,ignore_field1, ignore_field2));

Sample output if there is a mismatch

Output shows property names and respective values of compared objects

alert_id(1:2), city(Moscow:London)

Code (Java 8 and above):

 public static String diff(Object x1, Object x2, String ... ignored) throws Exception{
        final StringBuilder response = new StringBuilder();
        for (Method m:Arrays.stream(x1.getClass().getMethods()).filter(m->m.getName().startsWith("get")
        && m.getParameterCount()==0).collect(toList())){

            final String field = m.getName().substring(3).toLowerCase();
            if (Arrays.stream(ignored).map(x->x.toLowerCase()).noneMatch(ignoredField->ignoredField.equals(field))){
                Object v1 = m.invoke(x1);
                Object v2 = m.invoke(x2);
                if ( (v1!=null && !v1.equals(v2)) || (v2!=null && !v2.equals(v1))){
                    response.append(field).append("(").append(v1).append(":").append(v2).append(")").append(", ");
                }
            }
        }
        return response.length()==0?null:response.substring(0,response.length()-2);
    }
Zena answered 1/2, 2019 at 16:58 Comment(0)
L
0

For Unit testing I just serialize the object to a JSON string and compare it. For example with Gson:

import com.google.gson.GsonBuilder
import junit.framework.TestCase.assertEquals

class AssertEqualContent {
    companion object {
        val gson = GsonBuilder().create()

        fun assertEqualContent(message: String?, expected: Any?, actual: Any?) {
            assertEquals(message, gson.toJson(expected), gson.toJson(actual))
        }
    }
}

As the expected and actual object is supposed to be of the same type the field order will be the same.

Pros:

  • You will get a nice string comparison highligting exactly where the difference is.
  • No extra libraries (provided that you have a JSON library already)

Cons:

  • Objects of different types might produce the same JSON (but if they do, you might consider why do you have different classes for the same data.... and how they could end up being compared in a testing method :-)
Lichenin answered 20/5, 2021 at 13:44 Comment(0)
A
0

A lot has already been answered here, but I would like to highlight a simple approach that I have been using since long, hope it helps.

You can use Lombok https://projectlombok.org/ in your project by importing its library

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
    </dependency> 

It has a very beautiful annotation @Data which implements equals function when used on a data class in a way that it matches each field and value when called objA_classPerson.equals(objB_classPerson).

For example:

@Data
public class Person{

  private String firstName;
  private String lastName;
  private Date birthDateTime;
  private int age;
  private List<Person> siblings; 

}

So, in junit when you do Assertions.assertEquals(objA_classPerson, objB_classPerson), equals function implemented by @Data will handle the comparison for you.

@Data is generally safe to use, if you do not misuse the equals function. Be cautious when you create a HashMap out of a @Data class.

Alight answered 18/3 at 12:54 Comment(0)
T
-1

Can you put the comparision code you posted into some static utility method?

public static String findDifference(Type obj1, Type obj2) {
    String difference = "";
    if (obj1.getFieldA() == null && obj2.getFieldA() != null
            || !obj1.getFieldA().equals(obj2.getFieldA())) {
        difference += "Difference at field A:" + "obj1 - "
                + obj1.getFieldA() + ", obj2 - " + obj2.getFieldA();
    }
    if (obj1.getFieldB() == null && obj2.getFieldB() != null
            || !obj1.getFieldB().equals(obj2.getFieldB())) {
        difference += "Difference at field B:" + "obj1 - "
                + obj1.getFieldB() + ", obj2 - " + obj2.getFieldB();
        // (...)
    }
    return difference;
}

Than you can use this method in JUnit like this:

assertEquals("Objects aren't equal", "", findDifferences(obj1, obj));

which isn't clunky and gives you full information about differences, if they exist (through not exactly in normal form of assertEqual but you get all the info so it should be good).

Talaria answered 27/8, 2012 at 18:33 Comment(0)
T
-2

From your comments to other answers, I don't understand what you want.

Just for the sake of discussion, lets say that the the class did override the equals method.

So your UT will look something like:

SomeType expected = // bla
SomeType actual = // bli

Assert.assertEquals(expected, actual). 

And you are done. Moreover, you can not get the "full equality picture" if the assertion fails.

From what I understand, you are saying that even if the type did override equals, you would not be interested in it, since you want to get the "full equality picture". So there is no point in extending and overriding equals either.

So you have to options: either compare property by property, using reflection or hard-coded checks, I would suggest the latter. Or: compare human readable representations of these objects.

For example, you can create a helper class that serializes the type you wish tocompare to an XML document and than compare the resulting XML! in this case, you can visually see what exactly is equal and what is not.

This approach will give you the opportunity to look at the full picture but it is also relatively cumbersome (and a little error prone at first).

Trossachs answered 27/8, 2012 at 19:28 Comment(2)
It's possible my term "full equality picture" is confusing. Implementing equals() would indeed solve the problem. I'm interested in knowing all unequal fields (relevant to equality) at the same time, w/o having to re-run the test. Serializing the object is another possibility, but I don't necessarily need a deep equals. I'd like to utilize the equals() implementations of the properties if possible.Vivl
Great! You absolutely may utilize the equals of the properties, as you stated in your question. It seems that this is the most straightforward solution in this case but as you noted, the code can be very nasty.Trossachs
M
-6

You can override the equals method of the class like:

@Override
public int hashCode() {
    int hash = 0;
    hash += (app != null ? app.hashCode() : 0);
    return hash;
}

@Override
public boolean equals(Object object) {
    HubRule other = (HubRule) object;

    if (this.app.equals(other.app)) {
        boolean operatorHubList = false;

        if (other.operator != null ? this.operator != null ? this.operator
                .equals(other.operator) : false : true) {
            operatorHubList = true;
        }

        if (operatorHubList) {
            return true;
        } else {
            return false;
        }
    } else {
        return false;
    }
}

Well, if you want to compare two object from a class you must implement in some way the equals and the hash code method

Maryannemarybella answered 27/8, 2012 at 18:20 Comment(1)
but OP says that he doesn't want to override equals, he wants a better wayGuelph

© 2022 - 2024 — McMap. All rights reserved.