#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);
//...