Back to Repositories

Testing Reader and Writer JSON Operations in google/gson

This test suite validates the functionality of Gson’s Reader and Writer implementations, focusing on JSON serialization and deserialization operations. It ensures proper handling of primitive types, objects, and edge cases while maintaining stream integrity.

Test Coverage Overview

The test suite provides comprehensive coverage of Gson’s Reader and Writer capabilities.

Key areas tested include:
  • Serialization of primitive types and objects to JSON
  • Deserialization from JSON to Java objects
  • Handling of null values and type mismatches
  • Stream parsing of multiple JSON objects
  • Custom Appendable implementation support

Implementation Analysis

The testing approach employs JUnit framework features with systematic validation of both reading and writing operations.

Notable patterns include:
  • Setup of Gson instance using @Before annotation
  • Verification of serialization/deserialization symmetry
  • Error handling validation using assertThrows
  • Stream-based testing with CharArrayReader/Writer

Technical Details

Testing infrastructure includes:
  • JUnit test framework
  • Google Truth assertion library
  • Gson library with both default and custom configurations
  • Custom test types (BagOfPrimitives)
  • TypeToken for generic type testing
  • StringReader/Writer and CharArrayReader/Writer implementations

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test methods with clear purpose
  • Proper test setup and initialization
  • Comprehensive error case handling
  • Validation of both success and failure scenarios
  • Testing of edge cases and null handling
  • Clean separation of concerns between test cases

google/gson

gson/src/test/java/com/google/gson/functional/ReadersWritersTest.java

            
/*
 * Copyright (C) 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.gson.functional;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonStreamParser;
import com.google.gson.JsonSyntaxException;
import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.reflect.TypeToken;
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;

/**
 * Functional tests for the support of {@link Reader}s and {@link Writer}s.
 *
 * @author Inderjeet Singh
 * @author Joel Leitch
 */
public class ReadersWritersTest {
  private Gson gson;

  @Before
  public void setUp() throws Exception {
    gson = new Gson();
  }

  @Test
  public void testWriterForSerialization() {
    Writer writer = new StringWriter();
    BagOfPrimitives src = new BagOfPrimitives();
    gson.toJson(src, writer);
    assertThat(writer.toString()).isEqualTo(src.getExpectedJson());
  }

  @Test
  public void testReaderForDeserialization() {
    BagOfPrimitives expected = new BagOfPrimitives();
    Reader json = new StringReader(expected.getExpectedJson());
    BagOfPrimitives actual = gson.fromJson(json, BagOfPrimitives.class);
    assertThat(actual).isEqualTo(expected);
  }

  @Test
  public void testTopLevelNullObjectSerializationWithWriter() {
    StringWriter writer = new StringWriter();
    gson.toJson(null, writer);
    assertThat(writer.toString()).isEqualTo("null");
  }

  @Test
  public void testTopLevelNullObjectDeserializationWithReader() {
    StringReader reader = new StringReader("null");
    Integer nullIntObject = gson.fromJson(reader, Integer.class);
    assertThat(nullIntObject).isNull();
  }

  @Test
  public void testTopLevelNullObjectSerializationWithWriterAndSerializeNulls() {
    Gson gson = new GsonBuilder().serializeNulls().create();
    StringWriter writer = new StringWriter();
    gson.toJson(null, writer);
    assertThat(writer.toString()).isEqualTo("null");
  }

  @Test
  public void testTopLevelNullObjectDeserializationWithReaderAndSerializeNulls() {
    Gson gson = new GsonBuilder().serializeNulls().create();
    StringReader reader = new StringReader("null");
    Integer nullIntObject = gson.fromJson(reader, Integer.class);
    assertThat(nullIntObject).isNull();
  }

  @Test
  public void testReadWriteTwoStrings() throws IOException {
    Gson gson = new Gson();
    CharArrayWriter writer = new CharArrayWriter();
    writer.write(gson.toJson("one").toCharArray());
    writer.write(gson.toJson("two").toCharArray());
    CharArrayReader reader = new CharArrayReader(writer.toCharArray());
    JsonStreamParser parser = new JsonStreamParser(reader);
    String actualOne = gson.fromJson(parser.next(), String.class);
    assertThat(actualOne).isEqualTo("one");
    String actualTwo = gson.fromJson(parser.next(), String.class);
    assertThat(actualTwo).isEqualTo("two");
  }

  @Test
  public void testReadWriteTwoObjects() throws IOException {
    Gson gson = new Gson();
    CharArrayWriter writer = new CharArrayWriter();
    BagOfPrimitives expectedOne = new BagOfPrimitives(1, 1, true, "one");
    writer.write(gson.toJson(expectedOne).toCharArray());
    BagOfPrimitives expectedTwo = new BagOfPrimitives(2, 2, false, "two");
    writer.write(gson.toJson(expectedTwo).toCharArray());
    CharArrayReader reader = new CharArrayReader(writer.toCharArray());
    JsonStreamParser parser = new JsonStreamParser(reader);
    BagOfPrimitives actualOne = gson.fromJson(parser.next(), BagOfPrimitives.class);
    assertThat(actualOne.stringValue).isEqualTo("one");
    BagOfPrimitives actualTwo = gson.fromJson(parser.next(), BagOfPrimitives.class);
    assertThat(actualTwo.stringValue).isEqualTo("two");
    assertThat(parser.hasNext()).isFalse();
  }

  @Test
  public void testTypeMismatchThrowsJsonSyntaxExceptionForStrings() {
    Type type = new TypeToken<Map<String, String>>() {}.getType();
    var e = assertThrows(JsonSyntaxException.class, () -> gson.fromJson("true", type));
    assertThat(e)
        .hasCauseThat()
        .hasMessageThat()
        .startsWith("Expected BEGIN_OBJECT but was BOOLEAN");
  }

  @Test
  public void testTypeMismatchThrowsJsonSyntaxExceptionForReaders() {
    Type type = new TypeToken<Map<String, String>>() {}.getType();
    var e =
        assertThrows(
            JsonSyntaxException.class, () -> gson.fromJson(new StringReader("true"), type));
    assertThat(e)
        .hasCauseThat()
        .hasMessageThat()
        .startsWith("Expected BEGIN_OBJECT but was BOOLEAN");
  }

  /**
   * Verifies that passing an {@link Appendable} which is not an instance of {@link Writer} to
   * {@code Gson.toJson} works correctly.
   */
  @Test
  public void testToJsonAppendable() {
    class CustomAppendable implements Appendable {
      final StringBuilder stringBuilder = new StringBuilder();
      int toStringCallCount = 0;

      @CanIgnoreReturnValue
      @Override
      public Appendable append(char c) throws IOException {
        stringBuilder.append(c);
        return this;
      }

      @CanIgnoreReturnValue
      @Override
      public Appendable append(CharSequence csq) throws IOException {
        if (csq == null) {
          csq = "null"; // Requirement by Writer.append
        }
        append(csq, 0, csq.length());
        return this;
      }

      @CanIgnoreReturnValue
      @Override
      public Appendable append(CharSequence csq, int start, int end) throws IOException {
        if (csq == null) {
          csq = "null"; // Requirement by Writer.append
        }

        // According to doc, toString() must return string representation
        String s = csq.toString();
        toStringCallCount++;
        stringBuilder.append(s, start, end);
        return this;
      }
    }

    CustomAppendable appendable = new CustomAppendable();
    gson.toJson(Arrays.asList("test", 123, true), appendable);
    // Make sure CharSequence.toString() was called at least two times to verify that
    // CurrentWrite.cachedString is properly overwritten when char array changes
    assertThat(appendable.toStringCallCount).isAtLeast(2);
    assertThat(appendable.stringBuilder.toString()).isEqualTo("[\"test\",123,true]");
  }
}