Создание макета для глубоко вложенного класса с помощью AutoFixture / AutoMoq? - PullRequest
2 голосов
/ 29 марта 2019

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

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 , с минимальными усилиями, насколько это возможно.Я мог бы создать Mock и иметь .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>();

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

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

1 Ответ

2 голосов
/ 30 марта 2019

Со стандартным 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);

//...
...