【问题标题】:How to unit test a custom JsonConverter如何对自定义 JsonConverter 进行单元测试
【发布时间】:2020-10-22 19:31:36
【问题描述】:

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

然后我用一个接受typeof(DestinationConverter)[JsonConverter] 属性装饰了Destination 类。

当我使用 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

【问题讨论】:

  • 你为什么把CanConvert(objectType)放在ReadJson()中? ReadJson() 只有在已知转换器适用时才应该被调用。
  • 在以前的版本中,我遇到了在 SomeObject 类的第二个属性上调用该方法的问题(该类已编辑)。无论如何,删除它并不能解决问题。
  • 看来您需要提供 JsonReader 的实现。
  • ReadJson() 是反序列化的实现细节,你真的需要对那个方法进行单元测试吗?在反序列化期间无论如何都会调用它。
  • @dbc 的回答清楚地表明我需要它。此外,对组件进行单元测试就是要测试使用它的组件的实现细节。

标签: c# .net unit-testing json.net nunit


【解决方案1】:

您的基本问题是,当您创建JsonReader 时,它最初位于第一个标记之前。这在documentation for 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());

小提琴样本here.

完成后,我建议您将转换器重写如下:

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);
    }
}

通过在ReadJson() 中调用serializer.Deserialize&lt;int?&gt;(reader),您保证:

  • null 值在读取期间处理。

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

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

  • 阅读器将正确定位在正在阅读的令牌末端。 (如果令牌是原语,则无需高级阅读器,但对于更复杂的令牌,则需要。)

小提琴样本 #2 here.

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

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

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

  2. 格式不正确的 JSON 流会引发异常,例如一个被截断的。

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

【讨论】:

  • 谢谢先生!您的回答是使这个网站成为社区如此棒的资源的原因。我一坐到办公桌前就会执行您的建议。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-05-27
  • 1970-01-01
  • 2016-02-26
  • 1970-01-01
  • 1970-01-01
  • 2011-10-02
  • 1970-01-01
相关资源
最近更新 更多