簡體   English   中英

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

[英]How to mock (with Moq) an interface that is serialized by 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));

使它不必為了序列化而注冊你的模擬要困難得多 - 沒有強有力的方法來確定一個對象一個模擬,如果是,它應該是什么類型的模擬,如果它 ,我們應該如何使用mock的數據序列化它。 這只能通過Moq內部的一些非常討厭和脆弱的反射來完成,但是我們不能去那里。 不過,它可能會被添加到Moq本身作為一個功能。

這可以通過自定義序列化方式進一步擴展 - 在這里我假設我們只需序列化模擬類型的公共屬性即可。 這有點天真 - 如果你繼承接口,它將無法正常工作,例如,因為Type.GetProperties只獲取在接口本身聲明的屬性。 如果需要,可以將其作為練習留給讀者。

原則上可以將其擴展為反序列化,但有點棘手。 與具體實例相反,將其用於嘲弄目的是非常不尋常的。

如果您不擔心 JsonConvert.SerializeObject() 結果,您可以簡單地將以下行添加到您的代碼中:

_actionMock.As<ISerializable>();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM