Создание макета для глубоко вложенного класса с использованием AutoFixture / AutoMoq?

#c# #unit-testing #moq #autofixture #automoq

#c# #модульное тестирование #moq #автоматическая фиксация #automoq

Вопрос:

Я хотел бы написать модульный тест, который переопределяет некоторое свойство только для чтения, которое находится довольно глубоко в графе объектов. Я имею в виду метод, подобный этому:

 public string MethodToTest(IClassA classA)
{
    return classA.ClassB.ClassC.ClassD.Items[0].Name;
}
  

Где каждый ClassN реализует интерфейс IClassN и каждое свойство доступно только для чтения. Итак, примером интерфейса может быть:

IClassA

 public interface IClassA { IClassB ClassB { get; } }
  

И реализация будет выглядеть как:

ClassA

 public class ClassA : IClassA
{
    public ClassA() { ClassB = new ClassB(); }
    public IClassB ClassB { get; }
}
  

Я хочу переопределить значение, возвращаемое ClassA.ClassB.ClassC.ClassD.Items[0].Name, с минимальными усилиями, насколько это возможно. Я мог бы создать макет и иметь .Setup для возврата IClassB и перехода по всей цепочке, просто используя Moq. Но я бы хотел избежать этого, если это возможно.

Я пробовал много разных вещей, но безуспешно.

Попытка # 1

Я думал, что мог бы создать цепочку, используя fixture.Build()

 var moqItem = new Mock<IItem>();
moqItem.Setup(item => item.Name).Returns("My expected value");
var fakeClassD = fixture.Build<IClassD>()
                        .With(d => d.Items, new[] { moqItem.Object });
  

Очевидно, я опустил некоторые слои, но это не имеет значения. Это не удается, потому что свойства доступны только для чтения.

Попытка # 2

Затем я подумал, что мог бы «заморозить» конкретный экземпляр, и всякий раз, когда приспособление создавало объект, если оно видело что-то такого типа, оно использовало бы это. Я думал, что следую приведенному здесь примеру:https://blog.ploeh.dk/2010/03/17/AutoFixtureFreeze /

Это показывает такой код: var expectedName = fixture.Freeze("Name");

Основываясь на этом, я попытался сделать что-то вроде этого:

 var moqItem = new Mock<IItem>();
moqItem.Setup(x => x.Name).Returns("My expected value");
fixture.Freeze<IItem[]>(new IItem[] { moqItem.Object });
  

К сожалению, это даже не будет скомпилировано. Метод Freeze ожидает Func некоторого класса Composer типа IItem[], и я не смог выяснить, как это сделать. Если я удалю тип, аналогичный образцу кода, который я получу

 fixture.Freeze(new IItem[] { moqItem.Object });
  

Который также не удается скомпилировать.

Попытка # 3

 var moqItem = new Mock<IItem>();
moqItem.Setup(x => x.Name).Returns("My expected value");
fixture.Inject<IItem[]>(new IItem[] { moqItem.Object });
  

Очень похоже на попытку # 2 — только она компилируется. Я думал, что всякий раз, когда приспособлению нужен массив IItem [], он будет использовать тот, который я настроил. Но когда я вызываю

 var attempt3 = fixture.Create<IClassA>();
  

Поведение не то, на что я надеялся. attempt3.ClassB.ClassC.ClassD.Items не содержит моего макетного элемента.

TL; DR — Как я могу переопределить значение, возвращаемое из Item[0].Name , с наименьшим количеством кода / усилий?

Ответ №1:

При стандартном Moq из коробки то же самое можно сделать с помощью одной настройки, например

 //Arrange
var expected = "My expected value";
var mockA = new Mock<IClassA>();
// auto-mocking hierarchies (a.k.a. recursive mocks)
mockA.Setup(_ => _.ClassB.ClassC.ClassD.Items[0].Name)
    .Returns(expected);

//...

//Act
var actual = subject.MethodToTest(mockA.Object);

//...