Как выполнить модульное тестирование пользовательского JsonConverter - PullRequest
0 голосов
/ 06 ноября 2018

У меня есть полезная нагрузка json, которую я хочу десериализовать нетривиальным способом.

{
   "destinationId": 123
}

Целевой класс

public class SomeObject
{
    public Destination Destination { get; set; }
}

public class Destination
{
    public Destination(int destinationId)
    {
        Id = destinationId;
    }

    public int Id { get; set; }
}

Чтобы иметь возможность сделать это, я создал JsonConverter, который позаботится об этом.

Вот метод ReadJson:

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    if (CanConvert(objectType))
    {
        var value = reader.Value;

        if (value is long v)
        {
            // TODO: this might overflow
            return new Destination((int)v);
        }
    }

    return null;
}

Затем я украсил класс Destination атрибутом [JsonConverter], приняв typeof(DestinationConverter).

Это работает правильно, когда я использую JsonConvert.DeserializeObject<SomeObject>(myString) (см. Модульный тест ниже), но у меня возникают проблемы при создании успешного модульного теста специально для JsonConverter (см. Второй тест ниже).

[Test, AutoData]
public void SomeObject_is_correctly_deserialized(SomeObject testObject)
{
    var json = $@"{{""destinationId"":{testObject.Destination.Id}}}";

    Console.WriteLine($"json: {json}");

    var obj = JsonConvert.DeserializeObject<SomeObject>(json);

    Assert.That(obj.Destination.Id, Is.EqualTo(testObject.Destination.Id));
}

[Test, AutoData]
public void ReadJson_can_deserialize_an_integer_as_Destination(DestinationConverter sut, int testValue)
{
    JsonReader reader = new JTokenReader(JToken.Parse($"{testValue}"));

    var obj = sut.ReadJson(reader, typeof(Destination), null, JsonSerializer.CreateDefault());

    var result = obj as Destination;

    Assert.That(result, Is.Not.Null);
    Assert.That(result, Is.InstanceOf<Destination>());
    Assert.That(result.Id, Is.EqualTo(testValue));
}

Я искал способ надлежащего модульного тестирования преобразованного, но я нахожу только примеры людей, использующих целое DeserializeObject вместо простого тестирования преобразователя.

Спасибо за вашу помощь!

PS: я вставил весь необходимый код в .NET Fiddle: https://dotnetfiddle.net/oUXi6k

1 Ответ

0 голосов
/ 06 ноября 2018

Ваша основная проблема в том, что когда вы создаете JsonReader, он изначально позиционируется перед первым токеном. Это упоминается в документации для JsonToken:

Перечисление JsonToken

Указывает тип токена JSON.

Пользователи

  • None: 0 Возвращается JsonReader, если метод чтения не был вызван.

Таким образом, чтобы правильно выполнить юнит-тестирование вашего конвертера, вам нужно переместить читателя к первому токену объекта c #, который вы пытаетесь прочитать, например, вот так:

JsonReader reader = new JsonTextReader(new StringReader(json));
while (reader.TokenType == JsonToken.None)
    if (!reader.Read())
        break;

var obj = sut.ReadJson(reader, typeof(Destination), null, JsonSerializer.CreateDefault());

Образец скрипки здесь .

Сделав это, я бы посоветовал вам переписать ваш конвертер следующим образом:

public class DestinationConverter : JsonConverter
{
    public override bool CanConvert(System.Type objectType)
    {
        return objectType == typeof(Destination);
    }

    public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
    {
        var id = serializer.Deserialize<int?>(reader);
        if (id == null)
            return null;
        return new Destination(id.Value);
    }

    public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
    {
        // WriteJson() is never called with a null value, instead Json.NET writes null automatically.
        writer.WriteValue(((Destination)value).Id);
    }
}

Позвонив по номеру serializer.Deserialize<int?>(reader) внутри ReadJson(), вы гарантируете, что:

  • null значения обрабатываются во время чтения.

  • В случае некорректно сформированного JSON (например, усеченного файла) будет сгенерировано исключение.

  • Исключение будет выдано в случае недопустимого JSON (например, объект, в котором ожидалось целое число или переполнение целого).

  • Считыватель будет правильно расположен в конце считываемого токена. (В случаях, когда токен является примитивным, читателю не нужно продвигаться, но для более сложных токенов это необходимо.)

Образец скрипки № 2 здесь .

Вы также можете улучшить свои юнит-тесты, чтобы проверить, что:

  1. Считыватель правильно расположен после ReadJson(), например утверждая, что значения TokenType и Depth являются правильными, или даже подсчитывая количество токенов, оставшихся в потоке JSON, и утверждая, что это как ожидалось.

    Распространенной ошибкой при написании конвертера является неправильное расположение считывателя после преобразования. Когда это сделано, сам объект читается успешно, но все последующие объекты становятся поврежденными. Юнит-тестирование ReadJson() напрямую не поймает это, если вы не утверждаете, что читатель впоследствии правильно позиционируется

  2. Исключение выдается для плохо сформированного потока JSON, например, тот, который усечен.

  3. Выдается исключение для неожиданного токена JSON, например, когда встречается массив, где ожидается примитив.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...