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

时间:2019-05-03 08:43:15

标签: c# unit-testing json.net moq

我模拟了一个接口,该接口通过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)是由我无法访问的库中经过测试的代码调用的。

1 个答案:

答案 0 :(得分:1)

这在边缘有些粗糙,但是可以完成工作:

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仅获得在接口本身上声明的属性。如有需要,可以进行修正,留给读者练习。

原则上可以将其扩展为反序列化,但是有点棘手。与具体实例相反,出于嘲笑目的而需要它是非常不寻常的。