Junit best practice when asserting complex objects
Asked Answered
C

4

12

I am writing a lot JUnit tests these days for a legacy system.

Often I come to the question: What is the best way to assert complex Objects?

Here is my current code

public class SomeParserTest {

    @Test
    public void testParse() throws Exception {
        final SomeParser someParser = new SomeParser();
        someParser.parse("string from some file");

        final List<Result> listOfResults = someParser.getResults();
        assertThat(listOfResults, hasSize(5));

        assertResult(listOfResults.get(0), "20151223", 2411189L, isEmptyOrNullString(), "2.71", "16.99");
        assertResult(listOfResults.get(1), "20151229", 2411190L, isEmptyOrNullString(), "2.86", "17.9");
        assertResult(listOfResults.get(2), "20151229", 2411191L, is("1.26"), ".75", "23.95");
        assertResult(listOfResults.get(3), "20151229", 2411192L, is("2.52"), "1.5", "47.9");
        assertResult(listOfResults.get(4), "20151229", 2411193L, isEmptyOrNullString(), "2.71", "16.99");

        final List<SubResult> listofSubResuls = someParser.getSubResultOf(listOfResults.get(0));
        assertThat(listofSubResuls, hasSize(1));
        assertSubResult(listofSubResuls.get(0), 12.5D, "20151223", 1L, 14.87D, 16.99D, 0L, null, 67152L, "20151223", "2", 0L, "02411189", 56744349L);

        final List<SubResult> listofSubResuls1 = someParser.getListofBBBS(listOfResults.get(1));
        assertThat(listofSubResuls1, hasSize(2));
        assertSubResult(listofSubResuls1.get(0), 30.0D, "20151228", 1L, 12.53D, 17.9D, 0L, null, 67156L, "20151229", "2", 0L, "02411190", 56777888L);
        assertSubResult(listofSubResuls1.get(1), 33.3D, "20151228", 1L, 4.66D, 6.99D, 1L, "J", 67156L, "20151229", "2", 21L, "02411190", 56777889L);
//And 50 Lines more
    }

//  how to avoid so many parameters?
    private void assertSubResult(final SubResult subResult, final double someDouble, final String bestellDatum,
            final long someLong, final double someDouble2, final double someDouble3, final long someLong3,
            final String someString,
            final long someLong1,
            final String someString4, final String someString3, final long someLong4, final String rechnungsNummer,
            final long someLong2) {
        assertThat(subResult.getXXX(), is(nullValue()));
        assertThat(subResult.getXYX().getTag(), is(someDouble2));
        assertThat(subResult.getXYX(), is("some constant"));
//      and much more
    }

    //  how to avoid so many parameters?
    private void assertResult(final Result result, final String string1234, final long abc,
            final String string1, final String string12, final String string134) {
        assertThat(result.getXXX(), is(nullValue()));
        assertThat(result.getXYX().getTag(), is(someDouble2));
        assertThat(result.getXYX(), is("some constant"));
//      and much more
    }
}

There is no simple way to test each step of such a parser and I can't cahnge that much since it is legacy code...

Thanks for your Help!

Cower answered 30/12, 2015 at 14:19 Comment(2)
What I do to simplify this code is to test the toString(). This makes relatively brittle (which may or may not be desirable) but easy to maintain tests. For a List I turn the toString() into a multi-line output so the IDE can bring up a comparison window to easy see the differences.Manipur
This may work in many cases, but not in mine. The test would not cover logic in getters (yes it is bad practice, but its legacy code.) Also there is already a toString method which does not cover all the fields, yay...Cower
B
6

I would give assertj extracting feature a try, example:

// fellowshipOfTheRing is a List<TolkienCharacter>
assertThat(fellowshipOfTheRing).extracting("name", "age", "race.name")
                               .contains(tuple("Boromir", 37, "Man"),
                                         tuple("Sam", 38, "Hobbit"),
                                         tuple("Legolas", 1000, "Elf"));

The example is described in detail here : http://joel-costigliola.github.io/assertj/assertj-core-features-highlight.html#extracted-properties-assertion

You can also use specific comparison strategy to compare actual and expected results, lastly there is support for field by field comparison : isEqualToComparingFieldByField, isEqualToComparingOnlyGivenFields and isEqualToIgnoringGivenFields.

Hope it can help

Buffum answered 15/1, 2016 at 7:31 Comment(0)
P
4

As sisyphus I would suggest using hamcrest matchers.

But I recommend to program a custom matcher. Following line

assertResult(listOfResults.get(0), "20151223", 2411189L, isEmptyOrNullString(), "2.71", "16.99");

may then look like:

assertThat(listOfResults, contains(
  ResultMatcher.matchesResult().withFirstAttribute("20151223").andSecondAttribute(2411189L)...
  ... // here the matchers for the other elements of the list
));

You will need the custom matcher class ResultMatcher which should have following form:

class ResultMatcher extends TypeSafeMatcher<Result> {
   Matcher<String> firstAttribute = Matchers.any(String.class);
   Matcher<String> secondAttribute = Matchers.any(String.class);
   ...

   ResultMatcher withFirstAttribute(String firstAttribute) {
     this.firstAttribute = Matchers.equalTo(firstAttribute);
     return this;
   }

   ...

   public boolean matchesSafely(Result result) {
     if (!firstAttribute.matches(result.getFirstAttribute())) {
       return false
     }
     ...
     return true;
   }

}

There are some Advantages in this design:

  • you do not need an equals-method to be defined
  • you can define defaults for every attribute, so you can test only attributes you are interested in and match others by default
  • the test not have to check each attribute of the object (the matcher does this)
Primordial answered 30/12, 2015 at 16:20 Comment(0)
H
0

Instead of a long method signature, I would do something like. (pseudo code, disclaimer in case it does not compile):

class AssertResult {
    private int xxx;
    private String yyy;
    public AssertResult setXXX(int xxx) {
        this.xxx = xxx;
        return this;
    }
    public AssertResult setYYY(String yyy) {
        this.yyy = yyy;
        return this;
    }
    public void check(Result result) {
        assertThat(result.getXXX(), is(xxx));
        assertThat(result.getYYY(), is(yyy));
    }
 }

and then I could use it like this:

new AssertResult().setXXX(123).setYYY("asdasd").check(result);
Helbonnah answered 30/12, 2015 at 15:18 Comment(2)
Then I would have to write AssertResult.start(listOfResults.get(0)).assertXXX(123).assertYYY("asdasd"); instead of assertThat(subResult.getXXX(), is("something)); And I could miss some assertions, having them all in one place (that long method) forces me to make the asserion.Cower
changed the answer to do the asserts at the end. it was based on the builder pattern: en.wikipedia.org/wiki/Builder_patternHelbonnah
K
-1

You might want to take a look at hamcrest Matchers in conjunction with the assertThat() method from junit. You might find your test code looking a bit more like

assertThat(listOfResults.get(0), equalTo(expectedObject));

Alternatively, there's assertj which allows you to write helper classes which chain multiple separate assertions about the same object in a fluent manner, using the assertJ version of assertThat rather than JUnit's. That would allow your test code to look a bit more like

assertThat(listOfResults.get(0)).hasXXX("someString").hasYYY(1.234)
Kerekes answered 30/12, 2015 at 15:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.