Testing Data Serialisation/Deserialization in Java (with Moshi)

Featured image for sharing metadata for article

In Testing Data Serialisation/Deserialization in Java (with Jackson), I mentioned how you could test your serialisation layer if you're using Jackson.

One of the learnings from running Java AWS Lambdas is that Moshi is a great for your JSON (de)serialisation layer.

Because this is a pretty integral part of interacting with other services / being interacted with, we need to make sure these models are mapped correctly.

Testing these can be done in a few ways, but often I see them not being tested as low in the test pyramid as we can do.

Fortunately, there are a few options for how we can test that serialisation (from object to string) and deserialisation (from string to object) works.

For instance, let's say we have the class:

import com.squareup.moshi.Json;

public class TokenGrantDto {
  @Json(name = "access_token")
  private String accessToken;
  @Json(name = "refresh_token")
  private String refreshToken;
  @Json(name = "expires_in")
  private long expiresIn;
  private String scope;

  public String getAccessToken() {
    return accessToken;
  }

  public void setAccessToken(String accessToken) {
    this.accessToken = accessToken;
  }

  public String getRefreshToken() {
    return refreshToken;
  }

  public void setRefreshToken(String refreshToken) {
    this.refreshToken = refreshToken;
  }

  public long getExpiresIn() {
    return expiresIn;
  }

  public void setExpiresIn(long expiresIn) {
    this.expiresIn = expiresIn;
  }

  public String getScope() {
    return scope;
  }

  public void setScope(String scope) {
    this.scope = scope;
  }
}

We could write a test like so which validates that the serialisation is set up correctly:

import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

class TokenGrantDtoJsonTest {
  private final Moshi moshi = new Moshi.Builder().build();
  private final JsonAdapter<TokenGrantDto> adapter = moshi.adapter(TokenGrantDto.class);

  @Nested
  class Serialization {
    @Nested
    class HappyPath {

      private String asJson;

      @BeforeEach
      void setup() {
        TokenGrantDto dto = new TokenGrantDto();
        dto.setAccessToken("j.w.t");
        dto.setRefreshToken("r.j.w.t");
        dto.setScope("foo bar");
        dto.setExpiresIn(123L);

        asJson = adapter.toJson(dto);
      }

      @Test
      void scopeIsMapped() {
        assertThat(asJson).contains("\"scope\":\"foo bar\"");
      }

      @Test
      void expiresInIsMapped() {
        assertThat(asJson).contains("\"expires_in\":123");
      }

      @Test
      void accessTokenIsMapped() {
        assertThat(asJson).contains("\"access_token\":\"j.w.t\"");
      }

      @Test
      void refreshTokenIsMapped() {
        assertThat(asJson).contains("\"refresh_token\":\"r.j.w.t\"");
      }
    }
  }
}

This works, gives us incredibly fast feedback, but requires embedding JSON strings. I don't know about you, but I find that having to craft and maintain embedded JSON strings is the worst.

It also has the risk that at some point you'll need to introduce whitespace in each place if you change that way that the JSON is produced, and then we have to update a tonne of test data unnecessarily.

Or, we inject each of these snippets from files, which still leaves us with a fair bit of overhead.

Alternatively, we can utilise Jackson for test purposes only, so we can parse the JSON that is produced into the generic and handy JsonNode for easier validation:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

class TokenGrantDtoJsonTest {
  private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
  private final Moshi moshi = new Moshi.Builder().build();
  private final JsonAdapter<TokenGrantDto> adapter = moshi.adapter(TokenGrantDto.class);

  @Nested
  class Serialization {
    @Nested
    class HappyPath {

      private JsonNode asJson;

      @BeforeEach
      void setup() throws JsonProcessingException {
        TokenGrantDto dto = new TokenGrantDto();
        dto.setAccessToken("j.w.t");
        dto.setRefreshToken("r.j.w.t");
        dto.setScope("foo bar");
        dto.setExpiresIn(123L);

        asJson = OBJECT_MAPPER.readValue(adapter.toJson(dto), JsonNode.class);
      }

      @Test
      void scopeIsMapped() {
        assertThat(asJson.get("scope").textValue()).isEqualTo("foo bar");
      }

      @Test
      void expiresInIsMapped() {
        assertThat(asJson.get("expires_in").numberValue()).isEqualTo(123);
      }

      @Test
      void accessTokenIsMapped() {
        assertThat(asJson.get("access_token").textValue()).isEqualTo("j.w.t");
      }

      @Test
      void refreshTokenIsMapped() {
        assertThat(asJson.get("refresh_token").textValue()).isEqualTo("r.j.w.t");
      }
    }
  }
}

This gives us a pretty handy test harness, and we don't need to manage JSON strings, as well as having a bit more control over checking whether fields are absent:

@Test
void isNotSerialised() {
  assertThat(asJson.get("scope")).isNull();
}

Written by Jamie Tanna's profile image Jamie Tanna on , and last updated on .

Content for this article is shared under the terms of the Creative Commons Attribution Non Commercial Share Alike 4.0 International, and code is shared under the Apache License 2.0.

#java #testing #moshi #blogumentation.

This post was filed under articles.

This post is part of the series writing-better-tests.

Interactions with this post

Interactions with this post

Below you can find the interactions that this page has had using WebMention.

Have you written a response to this post? Let me know the URL:

Do you not have a website set up with WebMention capabilities? You can use Comment Parade.