Showing posts with label hamcrest. Show all posts
Showing posts with label hamcrest. Show all posts

Saturday, March 10, 2018

JUnit Hamcrest Matcher for JSON

This post shows how you can write JUnit tests to check if an object matches a JSON string. This is important if you are implementing REST services and want to test if your service produces the expected JSON response.

A useful library for comparing JSON objects is JSONassert. First, you have to convert your Java object into a JSON string (using Jackson, for example) and then compare it with your expected JSON string using JSONassert. (You could also convert your Java object into a JSONObject but I find that it's much easier to convert it into a string.)

The following snippet shows how you can compare an object (a List, in this case) against its JSON representation using JSONassert.

import org.skyscreamer.jsonassert.JSONAssert;
import com.fasterxml.jackson.databind.ObjectMapper;

List<String> fruits = Arrays.asList("apple", "banana");
String fruitsJSON = new ObjectMapper().writeValueAsString(fruits);
String expectedFruitsJSON = "[\"apple\", \"banana\"]";
JSONAssert.assertEquals(expectedFruitsJSON, fruitsJSON, true);

In order to make it simpler to write such unit tests, I have written a Hamcrest Matcher called IsEqualJSON for comparing JSON objects. It still uses JSONassert but allows you to express your tests in a more fluent way.

The following code shows how IsEqualJSON is used:

import static org.junit.Assert.*;
import static testutil.IsEqualJSON.*;

assertThat(Arrays.asList("apple", "banana"),
           equalToJSON("[\"apple\", \"banana\"]"));

// you can also have your expected JSON read from a file
assertThat(Arrays.asList("apple", "banana"),
           equalToJSONInFile("fruits.json"));

Here is the code for IsEqualJSON (also available in my GitHub Repository):

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import org.hamcrest.*;
import org.skyscreamer.jsonassert.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * A Matcher for comparing JSON.
 * Example usage:
 * <pre>
 * assertThat(new String[] {"foo", "bar"}, equalToJSON("[\"foo\", \"bar\"]"));
 * assertThat(new String[] {"foo", "bar"}, equalToJSONInFile("/tmp/foo.json"));
 * </pre>
 */
public class IsEqualJSON extends DiagnosingMatcher<Object> {

  private final String expectedJSON;
  private JSONCompareMode jsonCompareMode;

  public IsEqualJSON(final String expectedJSON) {
    this.expectedJSON = expectedJSON;
    this.jsonCompareMode = JSONCompareMode.STRICT;
  }

  @Override
  public void describeTo(final Description description) {
    description.appendText(expectedJSON);
  }

  @Override
  protected boolean matches(final Object actual,
                            final Description mismatchDescription) {
    final String actualJSON = toJSONString(actual);
    final JSONCompareResult result = JSONCompare.compareJSON(expectedJSON,
                                                             actualJSON,
                                                             jsonCompareMode);
    if (!result.passed()) {
      mismatchDescription.appendText(result.getMessage());
    }
    return result.passed();
  }

  private static String toJSONString(final Object o) {
    try {
      return o instanceof String ?
          (String) o : new ObjectMapper().writeValueAsString(o);
    } catch (final JsonProcessingException e) {
      throw new RuntimeException(e);
    }
  }

  private static String getFileContents(final Path path) {
    try {
      return new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
    } catch (final IOException e) {
      throw new RuntimeException(e);
    }
  }

  @Factory
  public static IsEqualJSON equalToJSON(final String expectedJSON) {
    return new IsEqualJSON(expectedJSON);
  }

  @Factory
  public static IsEqualJSON equalToJSONInFile(final Path expectedPath) {
    return equalToJSON(getFileContents(expectedPath));
  }

  @Factory
  public static IsEqualJSON equalToJSONInFile(final String expectedFileName) {
    return equalToJSONInFile(Paths.get(expectedFileName));
  }
}