[英]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.