首页 > 解决方案 > 如何对自定义 JsonConverter 进行单元测试

问题描述

我有一个 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;
}

然后我用一个[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

标签: c#.netunit-testingjson.netnunit

解决方案


您的基本问题是,当您创建 a 时JsonReader,它最初位于第一个标记之前。这在以下文档中JsonToken有所提及:

JsonToken 枚举

指定 JSON 令牌的类型。

成员

  • None0 如果未调用读取方法,则由 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)inside ReadJson(),您可以保证:

  • null在读取过程中处理值。

  • 如果 JSON 格式不正确(例如截断的文件),将引发异常。

  • 如果 JSON 无效(例如,预期为整数的对象,或整数溢出),将引发异常。

  • 阅读器将正确定位在正在阅读的令牌的末端。(在令牌是原语的情况下,阅读器不需要高级,但对于更复杂的令牌,它确实如此。)

样品小提琴#2在这里

您可能还想增强单元测试以检查:

  1. 阅读器被正确定位在 之后ReadJson(),例如通过断言TokenTypeandDepth是正确的,或者甚至计算 JSON 流中剩余的令牌数量并断言它是预期的。

    编写转换器时的一个常见错误是在转换后让阅读器定位错误。完成后,对象本身被成功读取,但所有后续对象都损坏了。ReadJson()除非您断言阅读器之后正确定位,否则直接进行 单元测试将无法捕捉到这一点。

  2. 对于格式不正确的 JSON 流,例如被截断的 JSON 流,将引发异常。

  3. 意外的 JSON 令牌会引发异常,例如,当在需要原语的地方遇到数组时。


推荐阅读