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

Kra*_*zek 9 .net c# nunit unit-testing json.net

我有一个 json 有效负载,我想以一种非平凡的方式反序列化。

{
   "destinationId": 123
}
Run Code Online (Sandbox Code Playgroud)

目标类是

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

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

    public int Id { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

为了能够做到这一点,我创建了一个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;
}
Run Code Online (Sandbox Code Playgroud)

然后我用一个[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));
}
Run Code Online (Sandbox Code Playgroud)

我一直在寻找一种方法来正确地对转换后的单元进行单元测试,但我只找到了使用整体DeserializeObject而不是仅仅测试转换器的人的例子。

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

dbc*_*dbc 7

您的基本问题是,当您创建 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());
Run Code Online (Sandbox Code Playgroud)

示例小提琴在这里

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • null 在读取期间处理值。

  • 如果 JSON 格式不正确(例如截断的文件),则会抛出异常。

  • 如果 JSON 无效(例如需要整数的对象,或整数溢出),则会抛出异常。

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

示例小提琴 #2在这里

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

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

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

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

  3. 对于意外的 JSON 标记会引发异常,例如,当遇到预期原始值的数组时。