Testing Data Serialisation/Deserialization in Java (with Gson)

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 using Gson is a good choice, albeit a bit slower than Moshi.

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:

package me.jvt.hacking.models;

import com.google.gson.annotations.SerializedName;

public class TokenGrantDto {
  @SerializedName("access_token")
  private String accessToken;
  @SerializedName("refresh_token")
  private String refreshToken;
  @SerializedName("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:

package me.jvt.hacking.models;

import com.google.gson.Gson;
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 Gson GSON = new Gson();

  @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 = GSON.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 use the underlying JsonObject and JsonElements inside Gson to validate things more easily:

package me.jvt.hacking.models;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
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 Gson GSON = new Gson();

  @Nested
  class Serialization {
    @Nested
    class HappyPath {

      private JsonObject 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 = JsonParser.parseString(GSON.toJson(dto)).getAsJsonObject();
      }

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

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

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

      @Test
      void refreshTokenIsMapped() {
        assertThat(asJson.get("refresh_token").getAsString()).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.has("scope")).isFalse();
}

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 #gson #blogumentation.

This post was filed under articles.

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

Related Posts

Other posts you may be interested in:

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.