Как смоделировать (с Moq) интерфейс, который сериализован Newtonsoft.json? - PullRequest
3 голосов
/ 03 мая 2019

Я издеваюсь над интерфейсом, из которого объект сериализуется проверенным кодом с помощью 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) вызывается проверенным кодом в библиотеке, к которой у меня нет доступа.

1 Ответ

1 голос
/ 03 мая 2019

Это немного грубо по краям, но это делает работу:

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 как функцию.

Это может быть дополнительно расширено с помощью пользовательских способов сериализации - здесь я предположил, что у нас все в порядке с простой сериализацией открытых свойств типа mocked. Это немного наивно - он не будет работать правильно, если вы наследуете интерфейсы, например, потому что Type.GetProperties получает только свойства, объявленные в самом интерфейсе. Исправление, если это необходимо, оставлено читателю в качестве упражнения.

Расширение этого для десериализации возможно в принципе, но немного сложнее. Было бы очень необычно нуждаться в этом в насмешливых целях, а не в конкретном случае.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...