c# - 如何模拟(使用 Moq)由 Newtonsoft.json 序列化的接口(interface)?

标签 c# unit-testing json.net moq

我模拟了一个接口(interface),该接口(interface)通过 Newtonsoft JsonConvert.SerializeObject 的测试代码序列化对象。

序列化程序抛出异常并出现以下错误:

Newtonsoft.Json.JsonSerializationException:检测到类型为“CaSTLe.Proxies.IActionProxy”的属性“Object”的自引用循环。路径“模拟”。

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 的内部结构进行一些非常讨厌和脆弱的反射(reflection)来完成,但我们不要去那里。不过,它可能会作为一项功能添加到 Moq 本身。

这可以通过自定义序列化方式进一步扩展——这里我假设我们只序列化模拟类型的公共(public)属性就可以了。这有点幼稚——如果您继承接口(interface),它就无法正常工作,例如,因为 Type.GetProperties 仅获取在接口(interface)本身上声明的属性。如果需要,修复它作为练习留给读者。

扩展它以进行反序列化原则上是可能的,但有点棘手。与具体实例相比,将其用于模拟目的是非常不寻常的。

关于c# - 如何模拟(使用 Moq)由 Newtonsoft.json 序列化的接口(interface)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55966212/

相关文章:

c# - 来自字典 C# 的 Json 格式

c# - 让 Json.NET + MongoDB Bson 一起玩得很好

c# - 如何为 Json.net 生成 json 对象的所有可能的 LINQ 字符串?

c# - LINQPad索引超出范围异常

c# - 将存储过程映射到实体对象及其子对象

c# - 无法启动服务并出现 net.tcp 绑定(bind)错误 10049

c# - FakeItEasy:重置假电话历史/忽略电话

c# - 什么 C# 类型代表 C++ float*?

visual-studio - Visual Studio 2012专业版-单元测试不起作用

c# - 停止调试器后如何启动清理方法?