如何对自定义JsonConverter进行单元测试

时间:2018-11-06 16:55:04

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

我有一个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]的{​​{1}}属性修饰了Destination类。

当我使用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)); } 的人的示例,而不仅仅是测试转换器。

感谢您的帮助!

PS:我将所有必需的代码粘贴到了.NET Fiddle中:https://dotnetfiddle.net/oUXi6k

1 个答案:

答案 0 :(得分:3)

您的基本问题是,当您创建JsonReader时,它最初位于第一个标记的之前documentation for JsonToken中提到了这一点:

  

JsonToken枚举

     

指定JSON令牌的类型。

     

会员

     
      
  • None0如果尚未调用read方法,则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);
    }
}

通过在serializer.Deserialize<int?>(reader)内部调用ReadJson(),可以保证:

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

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

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

  • 读取器将正确放置在正在读取的令牌的末尾。 (在令牌是原始令牌的情况下,不需要对阅读器进行高级处理,但对于更复杂的令牌,则需要这样做。)

样本小提琴#2 here

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

  1. 读取器已正确放置在ReadJson()之后,例如通过断言TokenTypeDepth是正确的,或者甚至算出JSON流中剩余的令牌数量并断言它是预期的。

    编写转换器时,常见的错误是在转换后使阅读器放错位置。完成此操作后,对象本身被成功读取,但是所有后续对象均损坏。除非您断言读取器之后的位置正确,否则直接进行单元测试ReadJson()不会抓住这一点。

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

  3. 抛出意外的JSON令牌异常,例如当遇到期望原始的数组时。

相关问题