如何对自定义 JsonConverter 进行单元测试
How to unit test a custom JsonConverter
我有一个 json 负载,我想以一种非平凡的方式反序列化。
{
"destinationId": 123
}
目标class是
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 class
这在我使用 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
您的基本问题是,当您创建 JsonReader
时,它最初位于第一个标记 之前 之前。 documentation for JsonToken
:
中提到了这一点
JsonToken Enumeration
Specifies the type of JSON token.
Members
None
: 0
This is returned by the JsonReader if a read method has not been called.
因此,为了正确地对您的转换器进行单元测试,您需要将 reader 推进到您尝试读取的 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());
示例 fiddle 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<int?>(reader)
,您保证:
null
值在读取期间处理。
如果格式不正确 JSON(例如截断的文件),将引发异常。
如果 JSON 无效(例如,需要整数或整数溢出的对象),将引发异常。
reader 将正确定位在正在读取的标记的末尾。 (在令牌是原始令牌的情况下,不需要推进 reader,但对于更复杂的令牌,它需要。)
示例 fiddle #2 here.
您可能还想增强单元测试以检查:
我有一个 json 负载,我想以一种非平凡的方式反序列化。
{
"destinationId": 123
}
目标class是
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 class
这在我使用 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
您的基本问题是,当您创建 JsonReader
时,它最初位于第一个标记 之前 之前。 documentation for JsonToken
:
JsonToken Enumeration
Specifies the type of JSON token.
Members
None
:0
This is returned by the JsonReader if a read method has not been called.
因此,为了正确地对您的转换器进行单元测试,您需要将 reader 推进到您尝试读取的 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());
示例 fiddle 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<int?>(reader)
,您保证:
null
值在读取期间处理。如果格式不正确 JSON(例如截断的文件),将引发异常。
如果 JSON 无效(例如,需要整数或整数溢出的对象),将引发异常。
reader 将正确定位在正在读取的标记的末尾。 (在令牌是原始令牌的情况下,不需要推进 reader,但对于更复杂的令牌,它需要。)
示例 fiddle #2 here.
您可能还想增强单元测试以检查: