0

I have JSON objects called Info which have only strings & look like this :

{
  "prop1":      "value1",
  "prop2":      "value1",
  "timestamp":  "2018-02-28T05:30:10.100Z",
  "prop4":      "value4",
  "prop_N":     "I have a total of 10 properties."
}

I want to compare them with an expected JSON object.

For this, I convert expected and actual JSON into Java objects/POJO. My expected JSON can have nulls in any of the 4 properties. If any expected JSON fields are null, then I want my code to not compare those fields. If they are not null, then compare them using String.equals(), with one exception: for the timestamp which is always UTC, we have to ignore the seconds & milliseconds.

The result of the comparison will be used in a JUnit assert method to check if the actual JSON received matches the expected JSON.

I have already implemented the code as shown below. But, there are two problems:

  1. It does not tell you which fields did not match (it simply returns true/false)
  2. It's very ugly because of the multiple if blocks.

Can someone suggest how to fix these problems ?

public boolean matchesInfo(Info givenInfo) throws Exception {

    if (this.getProp1() != null) {
        boolean prop1Matching = this.getProp1().equals(givenInfo.getProp1());
        if (prop1Matching == false){return false;}
    }

    // More if blocks like this. Very long and ugly.

    // For timestamp comparison, call a custom function to strip seconds, milliseconds.
}
glytching
  • 44,936
  • 9
  • 114
  • 120
rbqa
  • 33
  • 1
  • 7
  • 2
    Can someone please explain the down vote ? Without an explanation I don't know if you meant to up vote or down vote, or if you did not understand the question. I see that a lot of people in SO simply down vote a lot of good/useful questions. – rbqa Mar 13 '18 at 00:13
  • Seems to be similar to https://stackoverflow.com/q/2253750/1426227 – cassiomolin Mar 13 '18 at 10:43

2 Answers2

1

Rather than deserialising JSON into an object to facilitate comparison you could use JsonUnit to facilitate meaningful 'JSON comparison' in your test cases.

There are some examples below which address the points made in your question ... showing meaningful responses (i.e. name the mismatched field/value), ignoring null/absent fields. There are plenty more details in the docs, including the use of custom matchers to handle this requirement: "For the timestamp which is always UTC, we have to ignore the seconds & milliseconds.".

@Test
public void canAssertJsonEquality() {
    String expected = "{\n" +
            "  \"prop1\": \"value1\",\n" +
            "  \"prop2\": \"value1\"\n" +
            "}";
    String actual = "{\n" +
            "  \"prop1\": \"value1\",\n" +
            "  \"prop2\": \"value1\"\n" +
            "}";

    JsonAssert.assertJsonEquals(expected, actual);
}

@Test
public void canAssertJsonEqualityRegardlessOfPropertyOrder() {
    String expected = "{\n" +
            "  \"prop1\": \"value1\",\n" +
            "  \"prop2\": \"value1\"\n" +
            "}";
    String actual = "{\n" +
            "  \"prop2\": \"value1\",\n" +
            "  \"prop1\": \"value1\"\n" +
            "}";

    JsonAssert.assertJsonEquals(expected, actual);
}

@Test
public void canIgnoreFieldsWhichAreInActualButNotInExpected() {
    String expected = "{\n" +
            "  \"prop2\": \"value1\"\n" +
            "}";
    String actual = "{\n" +
            "  \"prop1\": \"value1\",\n" +
            "  \"prop2\": \"value1\"\n" +
            "}";

    JsonAssert.assertJsonEquals(expected, actual, JsonAssert.when(Option.IGNORING_EXTRA_FIELDS));
} 

// fails with ...
//      Different keys found in node "", expected: <[prop1, prop2]> but was: <[prop2]>. Missing: "prop1"
@Test
public void willFailIfActualIsMissingAProperty() {
    String expected = "{\n" +
            "  \"prop1\": \"value1\",\n" +
            "  \"prop2\": \"value1\"\n" +
            "}";
    String actual = "{\n" +
            "  \"prop2\": \"value1\"\n" +
            "}";

    JsonAssert.assertJsonEquals(expected, actual);
}

// fails with ...
//      Different value found in node "prop1", expected: <"value1"> but was: <"value2">.
@Test
public void willFailIfActualHasAnIncorrectValue() {
    String expected = "{\n" +
            "  \"prop1\": \"value1\",\n" +
            "  \"prop2\": \"value1\"\n" +
            "}";
    String actual = "{\n" +
            "  \"prop1\": \"value2\",\n" +
            "  \"prop2\": \"value1\"\n" +
            "}";

    JsonAssert.assertJsonEquals(expected, actual);
} 
glytching
  • 44,936
  • 9
  • 114
  • 120
0

There are a couple of ways to solve this with ModelAssert - https://github.com/webcompere/model-assert.

Firstly, we could just assert the fields we're interested in:

assertJson(actual)
   .at("/prop1").isText("value1")
   .at("/prop2").isText("value2");

Or, we could use an isEqualTo approach, marking certain paths as ignored

assertJson(actual)
   .where()
     .at("/prop2").isIgnored()
     .at("/prop3").isIgnored()
   .isEqualTo(expected);

assertJson will take in String, JsonNode and even a Map/POJO, which it serializes to JSON before comparison. Similarly isEqualTo also allows this.

ModelAssert also supports YML and can express its assertion as a Hamcrest matcher. It explains the differences when things fail.

Ashley Frieze
  • 4,993
  • 2
  • 29
  • 23