c# - 如何模拟(使用 Moq)由 Newtonsoft.json 序列化的接口?
问题描述
我模拟了一个接口,对象由 Newtonsoft JsonConvert.SerializeObject 的测试代码序列化。
序列化程序抛出异常并出现以下错误:
Newtonsoft.Json.JsonSerializationException:为“Castle.Proxies.IActionProxy”类型的属性“对象”检测到自引用循环。路径“模拟”。
Newtonsoft 尝试序列化代理对象 IActionProxy,它具有在该序列化对象上循环的属性 Mock。
奇怪地改变序列化程序选项
ReferenceLoopHandling = ReferenceLoopHandling.Serialize ( ot Ignore)
PreserveReferencesHandling = PreserveReferencesHandling.All
..不解决问题,序列化变成无限
感谢您对这个问题的帮助,我很乐意在这种情况下使用 Moq
更新:这是产生异常的示例代码:
Mock<IAction> _actionMock = new Mock<IAction>().SetupAllProperties();
Newtonsoft.Json.JsonConvert.SerializeObject( _actionMock.Object ); // JsonSerializationException (this line is in a method which i'm not responsible of )
// IAction is any interface with some properties
我们必须考虑序列化(SerializeObject)是由我无权访问的库中的测试代码调用的。
解决方案
这有点粗糙,但它可以完成工作:
public class JsonMockConverter : JsonConverter {
static readonly Dictionary<object, Func<object>> mockSerializers = new Dictionary<object, Func<object>>();
static readonly HashSet<Type> mockTypes = new HashSet<Type>();
public static void RegisterMock<T>(Mock<T> mock, Func<object> serializer) where T : class {
mockSerializers[mock.Object] = serializer;
mockTypes.Add(mock.Object.GetType());
}
public override bool CanConvert(Type objectType) => mockTypes.Contains(objectType);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) =>
throw new NotImplementedException();
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
if (!mockSerializers.TryGetValue(value, out var mockSerializer)) {
throw new InvalidOperationException("Attempt to serialize unregistered mock.");
}
serializer.Serialize(writer, mockSerializer());
}
}
一个方便使用的小扩展方法:
internal static class MockExtensions {
public static Mock<T> RegisterForJsonSerialization<T>(this Mock<T> mock) where T : class {
JsonMockConverter.RegisterMock(
mock,
() => typeof(T).GetProperties().ToDictionary(p => p.Name, p => p.GetValue(mock.Object))
);
return mock;
}
}
设置如下:
JsonConvert.DefaultSettings = () => new JsonSerializerSettings {
Converters = new[] { new JsonMockConverter() }
};
现在以下代码有效:
public interface IAction {
int IntProperty { get; set; }
}
var actionMock = new Mock<IAction>()
.SetupAllProperties()
.RegisterForJsonSerialization();
var action = actionMock.Object;
action.IntProperty = 42;
Console.WriteLine(JsonConvert.SerializeObject(action));
让你不必为序列化注册你的模拟要困难得多——没有可靠的方法来确定一个对象是一个模拟,如果是,它应该模拟什么类型,如果它是,我们应该如何使用模拟的数据序列化它。这只能通过对 Moq 内部结构的一些非常讨厌和脆弱的反思来完成,但我们不要去那里。不过,它可能会作为一项功能添加到 Moq 本身。
这可以通过自定义的序列化方式进一步扩展——在这里我假设我们只需序列化模拟类型的公共属性就可以了。这有点幼稚——例如,如果您继承接口,它将无法正常工作,因为Type.GetProperties
只获取在接口本身上声明的属性。如果需要,修复它作为练习留给读者。
原则上可以扩展它以进行反序列化,但有点棘手。与具体实例相反,出于模拟目的需要它是非常不寻常的。
推荐阅读
- python - 合并图像块并显示图像编号
- sparql - CONSTRUCT CSPARQL 查询中的序列路径失败
- android - Android .apk 文件中缺少资产
- oracle - 我收到(由于初始化提供程序时出错,测试连接失败)错误:E_UNEXPECTED(0x8000FFFF)。关于 OLE DB 的 oracle 提供程序
- async-await - 外部 Http 调用上的无服务器框架/Lambda 错误
- laravel - Laravel 6,计算每个部门的员工人数
- javascript - 如何将数据发布到后端服务器以存储到数据库中?
- node.js - 在 nodejs 模块中运行 newman
- java - System.out.print("Calculator") 后跟 Scanner 或 BufferReader 时不显示输出,后者在 IntelliJ IDEA 中采用 System.in
- variables - 关于 grails 域类变量的简单问题