Создание гибрида макета и анонимного объекта с использованием EG MOQ и AutoFixture?
-
27-10-2019 - |
Вопрос
Я столкнулся с классом во время моей работы, которая выглядит так:
public class MyObject
{
public int? A {get; set;}
public int? B {get; set;}
public int? C {get; set;}
public virtual int? GetSomeValue()
{
//simplified behavior:
return A ?? B ?? C;
}
}
Проблема в том, что у меня есть какой-то код, который обращается к A, B и C и вызывает метод GetSomeValue () (теперь, я бы сказал, что это не хороший дизайн, но иногда мои руки связаны ;-)). Я хочу создать макет этого объекта, который, в то же время, имеет установленное значение, B и C для некоторых значений. Итак, когда я использую MOQ как таковой:
var m = new Mock<MyObject>() { DefaultValue = DefaultValue.Mock };
Позволяет мне настроить результат на методе GetSomeValue (), но все свойства установлены на NULL (и настройка всех из них с использованием Setup () довольно громоздко, поскольку реальный объект - это неприятный объект данных и обладает большим количеством свойств, чем в выше упрощенный пример).
Итак, с другой стороны, используя AutoFixture, как это:
var fixture = new Fixture();
var anyMyObject = fixture.CreateAnonymous<MyObject>();
Оставляет меня без способности Стуть призыв к методу GetSomeValue ().
Есть ли способ объединить их, иметь анонимные значения и возможность настроить результаты вызова?
Редактировать
Основываясь на ответе Nemesv, я получил следующий метод утилиты (надеюсь, я понял это правильно):
public static Mock<T> AnonymousMock<T>() where T : class
{
var mock = new Mock<T>();
fixture.Customize<T>(c => c.FromFactory(() => mock.Object));
fixture.CreateAnonymous<T>();
fixture.Customizations.RemoveAt(0);
return mock;
}
Решение
Этот является На самом деле можно сделать с автофиклением, но это требует немного настройки. Точки расширения все там, но я признаю, что в этом случае решение не особенно обнаружено.
Это становится еще сложнее, если вы хотите, чтобы он работал с вложенными/сложными типами.
Учитывая MyObject
класс выше, а также это MyParent
учебный класс:
public class MyParent
{
public MyObject Object { get; set; }
public string Text { get; set; }
}
Эти модульные тесты проходят:
public class Scenario
{
[Fact]
public void CreateMyObject()
{
var fixture = new Fixture().Customize(new MockHybridCustomization());
var actual = fixture.CreateAnonymous<MyObject>();
Assert.NotNull(actual.A);
Assert.NotNull(actual.B);
Assert.NotNull(actual.C);
}
[Fact]
public void MyObjectIsMock()
{
var fixture = new Fixture().Customize(new MockHybridCustomization());
var actual = fixture.CreateAnonymous<MyObject>();
Assert.NotNull(Mock.Get(actual));
}
[Fact]
public void CreateMyParent()
{
var fixture = new Fixture().Customize(new MockHybridCustomization());
var actual = fixture.CreateAnonymous<MyParent>();
Assert.NotNull(actual.Object);
Assert.NotNull(actual.Text);
Assert.NotNull(Mock.Get(actual.Object));
}
[Fact]
public void MyParentIsMock()
{
var fixture = new Fixture().Customize(new MockHybridCustomization());
var actual = fixture.CreateAnonymous<MyParent>();
Assert.NotNull(Mock.Get(actual));
}
}
Что в Mockybridcustomization? Этот:
public class MockHybridCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(
new MockPostprocessor(
new MethodInvoker(
new MockConstructorQuery())));
fixture.Customizations.Add(
new Postprocessor(
new MockRelay(t =>
t == typeof(MyObject) || t == typeof(MyParent)),
new AutoExceptMoqPropertiesCommand().Execute,
new AnyTypeSpecification()));
}
}
А MockPostprocessor
, MockConstructorQuery
а также MockRelay
классы определены в Расширение автомобиля Для автофиктуры, поэтому вам нужно добавить ссылку на эту библиотеку. Однако обратите внимание, что не обязательно добавлять AutoMoqCustomization
.
А AutoExceptMoqPropertiesCommand
Класс также построен на заказ:
public class AutoExceptMoqPropertiesCommand : AutoPropertiesCommand<object>
{
public AutoExceptMoqPropertiesCommand()
: base(new NoInterceptorsSpecification())
{
}
protected override Type GetSpecimenType(object specimen)
{
return specimen.GetType();
}
private class NoInterceptorsSpecification : IRequestSpecification
{
public bool IsSatisfiedBy(object request)
{
var fi = request as FieldInfo;
if (fi != null)
{
if (fi.Name == "__interceptors")
return false;
}
return true;
}
}
}
Это решение обеспечивает общее решение для вопроса. Тем не менее, это не было тщательно протестировано, поэтому я бы хотел получить отзывы об этом.
Другие советы
Наверное, есть лучше, почему, но это работает:
var fixture = new Fixture();
var moq = new Mock<MyObject>() { DefaultValue = DefaultValue.Mock };
moq.Setup(m => m.GetSomeValue()).Returns(3);
fixture.Customize<MyObject>(c => c.FromFactory(() => moq.Object));
var anyMyObject = fixture.CreateAnonymous<MyObject>();
Assert.AreEqual(3, anyMyObject.GetSomeValue());
Assert.IsNotNull(anyMyObject.A);
//...
Первоначально я пытался использовать fixture.Register(() => moq.Object);
вместо fixture.Customize
но он регистрирует функцию создателя OmitAutoProperties()
Так что это не сработает для вас.
По состоянию на 3.20.0 вы можете использовать AutoConfiguredMoqCustomization
. Анкет Это автоматически настраивает все макет, так что возвращаемые значения их членов генерируются автофикцией.
var fixture = new Fixture().Customize(new AutoConfiguredMoqCustomization());
var mock = fixture.Create<Mock<MyObject>>();
Assert.NotNull(mock.Object.A);
Assert.NotNull(mock.Object.B);
Assert.NotNull(mock.Object.C);