Search code examples
javajsonjacksonassertj

Using AssertJ to assert multiple JsonNode paths on the same object?


I'm using assertj-core 3.9.1, but I also tested this with the latest available to me, 3.16.1.

I'm trying to make some assertj assertions a little cleaner. We often assert multiple subproperties at a non-trivial hierarchy level, for various kinds of objects. I've been using "extracting" to pull simple properties from an object, but what I'm trying to do now is a little more complicated, and it's not working. What I'm seeing SEEMS like a bug in assertj, but I'm not sure why it's doing what it's doing.

I'm starting with something like this:

assertThat(expression).at("/content/liability").asText()).isEqualTo("CRU");
assertThat(expression).at("/content/enterpriseType").asText()).isEqualTo("GBS");

I'd like to instead do something like this:

assertThat(expression)
    .extracting(jn -> jn.at("/content/liability").asText(),
                jn -> jn.at("/content/enterpriseType").asText())
    .containsExactly("CRU", "GBS");

This fails, basically saying that I expected "[]" (an empty list) to be equal to ["CRU", "GBS"].

I still have the original assertions above this, using the same expression, so I know the data is there. The expression returns a structure like this:

{
  "content": {
    ...
    "enterpriseType": "GBS",
    ...
    "liability": "CRU",
    ...
  }
}

So, I then tried changing the assertion to this:

assertThat(expression))
    .extracting(jn -> textAtPath(jn, "/content/liability"),
                jn -> textAtPath(jn, "/content/enterpriseType"))
    .containsExactly(tuple("CRU", "GBS"));

With:

private String textAtPath(JsonNode node, String path) {
    return node.at(path).asText();
}

And then I ran the test with a breakpoint in "textAtPath". What I saw there looked normal, until I looked closer. When I viewed the value of "node", I saw something that looked very much like the structure I showed above, but not quite:

{
  ...
  "enterpriseType": "GBS",
  ...
  "liability": "CRU",
  ...
}

For some reason, the value passed to the lambda passed to "extracting()" was the CHILD node of the original structure.

I tried changing the test so the two path strings were "/liability" and "/enterpriseType". The test passed. I'm not going to leave it like this, because this just doesn't make sense.


Solution

  • I believe this is discussed in https://github.com/joel-costigliola/assertj-core/issues/1954.

    The subtlety here is that if expression is an iterable, assertThat(expression) will provide iterable assertions and extracting will be applied on the iterable elements.

    On the other hand if assertThat(expression) has returned object assertion, extracting would be applied to expression.

    I also strongly suggest to use JsonUnit for JSON assertions.