如何对自定义JsonConverter进行单元测试 [英] How to unit test a custom JsonConverter

查看:61
本文介绍了如何对自定义JsonConverter进行单元测试的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个JSON有效负载,想以一种简单的方式反序列化.

I have a json payload that I want to deserialize in a non-trivial way.

{
   "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来处理它.

To be able to do so, I've created a JsonConverter that takes care of it.

这是ReadJson方法:

Here is the ReadJson method:

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类.

I have then decorated the Destination class with a [JsonConverter] attribute accepting a typeof(DestinationConverter).

当我使用JsonConvert.DeserializeObject<SomeObject>(myString)时,此方法可以正常工作(请参见下面的单元测试),但是我在为JsonConverter专门创建成功的单元测试时遇到了问题(请参见下面的第二项测试).

This works correctly when I use JsonConvert.DeserializeObject<SomeObject>(myString) (see unit test below) but I'm having issues creating a successful unit test for the JsonConverter specifically (see second test below).

[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的人的示例,而不仅仅是测试转换器.

I've been googling for a way to properly unit-test a converted but I only find examples of people using the whole DeserializeObject instead of just testing the converter.

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

PS: I pasted all the necessary code in a .NET Fiddle: https://dotnetfiddle.net/oUXi6k

推荐答案

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

Your basic problem is that, when you create a JsonReader, it is initially positioned before the first token. This is alluded to in the documentation for JsonToken:

JsonToken枚举

指定JSON令牌的类型.

Specifies the type of JSON token.

会员

  • None:0如果尚未调用read方法,则JsonReader将返回此值.
  • None: 0 This is returned by the JsonReader if a read method has not been called.

因此,要对转换器进行正确的单元测试,您需要将读取器提升到您尝试读取的c#对象的第一个令牌,例如像这样:

Thus, to properly unit-test your converter, you need to advance the reader to the first token of the c# object you are trying to read, e.g. like so:

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

样本小提琴此处.

完成此操作后,建议您按如下所示重写转换器:

Having done that, I would suggest you rewrite your converter as follows:

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<int?>(reader),您可以保证:

By calling serializer.Deserialize<int?>(reader) inside ReadJson(), you guarantee that:

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

  • null values are handled during reading.

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

An exception will get thrown in case of not-well-formed JSON (e.g. a truncated file).

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

An exception will get thrown in case of invalid JSON (e.g. an object where an integer was expected, or an integer overflow).

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

The reader will be correctly positioned at the end of the token being read. (In cases where the token is a primitive the reader does not need to be advanced, but for more complex tokens, it does.)

小提琴样本2 #a 此处.

Sample fiddle #2 here.

您可能还希望增强单元测试以检查以下内容:

You might also want to enhance your unit tests to check that:

  1. 读取器已正确放置在ReadJson()之后,例如通过声明 TokenType

  1. The reader is correctly positioned after ReadJson(), e.g. by asserting the the TokenType and Depth are correct, or even counting the number of tokens remaining in the JSON stream and asserting it is as expected.

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

A common mistake when writing a converter is to leave the reader mis-positioned after conversion. When this is done, the object itself is read successfully but all subsequent objects become corrupt. Unit-testing ReadJson() directly will not catch this unless you assert that the reader is correctly positioned afterwards.

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

An exception is thrown for a not-well-formed JSON stream, e.g. one that is truncated.

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

An exception is thrown for an unexpected JSON token, e.g. when an array is encountered where a primitive is expected.

这篇关于如何对自定义JsonConverter进行单元测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆