4

My use case is that I am writing a document parser (for ReqIF documents) and want to compare that the parsed object contains the expected elements. For this, I want to write a JUnit Test.

Effectively, what I am looking for is an assertion method that compares two lists of objects with values and passes when both lists contain (in any order) objects that have the same values as the reference list.

To abstract that, consider this example:

Apple referenceApple1 = new Apple(color="red");
Apple referenceApple2 = new Apple(color="green");
Apple parsedApple1 = new Apple(color="red");
Apple parsedApple2 = new Apple(color="green");
Apple badApple = new Apple(color="brown");

List<Apple> referenceList = new List<Apple>(referenceApple1, referenceApple2);
List<Apple> correctlyParsedList1 = new List<Apple>(parsedApple1, parsedApple2);
List<Apple> correctlyParsedList2 = new List<Apple>(parsedApple2, parsedApple1);
List<Apple> wronglyParsedList1 = new List<Apple>(parsedApple1, badApple);
List<Apple> wronglyParsedList2 = new List<Apple>(parsedApple1, parsedApple2, parsedApple1);
List<Apple> wronglyParsedList3 = new List<Apple>(parsedApple2, parsedApple2);

I am looking for an assertion method that passes when comparing any of the above correctlyParsedList* with the referenceList, but fails when comparing any of the above wronglyParsedList* with the referenceList.

Currently, the closest I've gotten to is this:

assertEquals(referenceList.toString(), correctlyParsedList1.toString())

However, that will fail as soon as the objects are in another order.

//Will fail, but I want a method that will compare these and pass
assertEquals(referenceList.toString(), correctlyParsedList2.toString())

Notably, the following will also fail since the Apples, while containing the same values, are not instances of the same object:

assertThat(correctlyParsedList1, is(referenceList));

//Throws This error:
java.lang.AssertionError: 
Expected: is <[[color="red"], [color="green"]]>
     but: was <[[color="red"], [color="green"]]>

Is there a simple way to make such an assertion in JUnit? I know I could write a custom assertion method for this iterating over the objects, but somehow it feels like this would be a common use case that should have a pre-defined assertion method that throws expressive assertion errors.


EDIT ~ CONCRETIZATION

What I am actually trying to do with this abstract example is to parse a complex XML using JDOM2, and I want to assert that the attributes of a tag being parsed equal those that exist in the sample document I give as an input. Since this is XML, the order of the attributes is irrelevant, as long as they have the correct values.

So effectively, what I am comparing in this practical use case is two List<Attribute>, with Attribute coming from org.jdom2.Attribute.

The complete makeshift testcase with which I am currently unhappy because it will fail if the order of attributes changes but should not is as follows:

    @Test
    public void importXML_fromFileShouldCreateXML_objectWithCorrectAttributes() throws JDOMException, IOException {
        testInitialization();
        List<Attribute> expectedAttributes = rootNode.getAttributes();

        XML_data generatedXML_data = xml_importer.importXML_fromFile(inputFile);
        List<Attribute> actualAttributes = generatedXML_data.attributes;

        assertEquals(expectedAttributes.toString(), actualAttributes.toString());
    }

The concrete error I get when trying to make that assertion with assertThat(expectedAttributes, is(actualAttributes)) is:

java.lang.AssertionError: 
Expected: is <[[Attribute: xsi:schemaLocation="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd http://www.omg.org/spec/ReqIF/20110401/reqif.xsd"], [Attribute: xml:lang="en"]]>
    but: was <[[Attribute: xsi:schemaLocation="http://www.omg.org/spec/ReqIF/20110401/reqif.xsd http://www.omg.org/spec/ReqIF/20110401/reqif.xsd"], [Attribute: xml:lang="en"]]>
Kira Resari
  • 1,718
  • 4
  • 19
  • 50

5 Answers5

5

You should use containsAll to check whether one list has all items from another, as long as you have proper equals implementation for your Apple this should work:

    // Covers case where all items that are from correct list are in reference as well
    // but also some additional items exist that are not in correct list
    assertEquals(correctlyPassedList.size(), referenceList.size());
    // Checks if each element is contained in list
    assertTrue(referenceList.containsAll(correctlyPassedList));

UPDATE

Regarding your latest comment probably this would work best:

    // Sort both lists using comparator based on id or whatever field is relevant
    referenceList.sort(Comparator.comparingInt(Apple::getId));
    correctlyParsedList.sort(Comparator.comparingInt(Apple::getId));
    // Check if they are equal
    assertEquals(correctlyParsedList, referenceList);
FilipRistic
  • 2,661
  • 4
  • 22
  • 31
  • I'm not sure what you mean with "as long as you have proper equals implementation for your `Apple`". I have not had to deal with this sort of thing before Could you kindly elaborate about how the Apple class would have to look for this to work? My current `Apple` is a simple data object that only contains its id and a number of other values, and with that, the assertion fails. Also, because `assertTrue` is used, the resulting Assertation error is not very expressive (as opposed to AssertEquals, which displays the mismatch in case of a mismatch). – Kira Resari Feb 21 '20 at 12:21
  • 1
    If you don't override equals in your class, then equals checks if 2 instances have same reference, you should override equals to take id into account, if you want more expressive message sorting would work better. – FilipRistic Feb 21 '20 at 13:16
  • That sounds reasonable, and I would like to do that. However, in practice, I have just learned that the "Apple" from above example is an external class that I can't modify, so I can't override the `equals` function in it. What can I do in this case? (In practice, I am using JDOM2 to import a complex XML, and what I am trying to do is compare the parsed attributes to make sure the correct attributes are parsed; I'll add this to the question as clarification) – Kira Resari Feb 24 '20 at 07:28
  • 1
    @KiraResari Ok that information helps a lot, I updated the answer hope it helps you. – FilipRistic Feb 25 '20 at 12:07
  • Thanks for the suggestion and your efforts. I've once again tried to implement this, only to realize that the things that this test is supposed to compare are actually strings. So it's a list of attributes with strings. I'll update my initial question to reflect this and give samples. – Kira Resari Feb 26 '20 at 07:08
1

If you do not care for the order of the elements, you can sort the lists before comparing them.

SergiyKolesnikov
  • 7,369
  • 2
  • 26
  • 47
1

I personally prefer to always use AssertJ.

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

Either of these two assertions should do the trick!

assertThat(correctlyParsedList1).containsOnlyOnce(referenceApple1, referenceApple2);
assertThat(correctlyParsedList1).containsOnlyOnceElementsOf(referenceList);
Ahmad Abdelghany
  • 11,983
  • 5
  • 41
  • 36
1

Drawing on @FilipRistic's answer, this is what worked for me:

Sort both lists using comparator based on id or whichever field is relevant:

referenceList.sort(Comparator.comparingInt(Apple::getId));
correctlyParsedList.sort(Comparator.comparingInt(Apple::getId));

Then simply:

assertThat(referenceList).usingRecursiveComparison().isEqualTo(correctlyParsedList);
Oozeerally
  • 842
  • 12
  • 24
0

I think the accepted way now is to use Hamcrest library and use one of its Matchers.containsInAnyOrder (Any class assuming there is an equals method) I could populate this with a specific set of values, but feel this is more generic.

List<T> values; // literally add in any order
List<T> containsValuesInAnyOrder; // literally add in any order
MatcherAssert.assertThat(values, containsValuesInAnyOrder);
MarkAddison
  • 515
  • 2
  • 9