Moq.Mock <Expression <Func <T, bool >>>() - как настроить выражения в макет с использованием Moq
Я прочитал много других вопросов по этой теме, и я до сих пор не могу найти решение моей проблемы, поэтому я решил разоблачить свое дело.
У меня есть этот интерфейс
public interface IRepository<T> where T : class, IEntity
{
IQueryable<T> Find(Expression<Func<T, bool>> predicate);
T FindIncluding(int id, params Expression<Func<T, object>>[] includeProperties);
}
И это основная структура метода, который содержит Mock, который я хотел бы настроить
public PeopleController CreatePeopleController()
{
var mockUnitofWork = new Mock<IUnitOfWork>();
var mockPeopleRepository = new Mock<IRepository<Person>>();
mockPeopleRepository.Setup(r=>r.Find().Returns(new Person(){});
mockUnitofWork.Setup(p => p.People).Returns(mockPeopleRepository.Object);
return new PeopleController(mockUnitofWork.Object);
}
Я пытаюсь настроить Mock следующим образом:
public PeopleController CreatePeopleController()
{
var mockUnitofWork = new Mock<IUnitOfWork>();
var mockPeopleRepository = new Mock<IRepository<Person>>();
mockPeopleRepository.Setup(r=>r.Find(It.isAny<Expression<Func<Person,bool>>>()).Single()).Returns(new Person(){});
mockUnitofWork.Setup(p => p.People).Returns(mockPeopleRepository.Object);
return new PeopleController(mockUnitofWork.Object);
}
Но система всегда выдает одно и то же исключение "System.NotSupportedException: выражение ссылается на метод, который не принадлежит к издеваемому объекту..."
Также я хотел бы добавить, что я использую MSTest и Moq
Я знаю, что настройка Mock с помощью Expression непростая и не рекомендуется, но для меня это очень важно, потому что "Найти" - это метод, который я использую много в своем приложении.
Ответы
Ответ 1
Проблема заключается в том, что вы пытаетесь настроить метод Single() расширения как часть вашего издевательства. У установочного вызова должен быть результат вашего метода - не результат вашего метода с последующим применением метода расширения. Я бы попробовал это:
[TestMethod]
public void MyTestMethod()
{
var myMock = new Mock<IRepository<Person>>();
myMock.Setup(r => r.Find(It.IsAny<Expression<Func<Person, bool>>>())).Returns(new List<Person>() { new Person() }.AsQueryable());
Assert.IsTrue(true);
}
Здесь вы просто обходите свой метод Find() с настройкой и делаете все остальное в предложении Returns(). Я бы предложил этот подход в целом. Программа установки должна точно отразить ваш издеваемый предмет, и вы можете сделать кучу черной магии для вызова Returns() (или Throws() или любого другого), чтобы заставить его делать то, что вы хотите.
(Когда я запустил этот код в VS, он прошел, поэтому он не выбрасывал исключение)
Ответ 2
Использование Moq It.IsAny<>
без .CallBack
заставляет вас писать код, который не распространяется на ваш тест. Вместо этого он позволяет любому запросу/выражению вообще проходить, делая ваш макет в основном бесполезным с точки зрения модульного тестирования.
Решение: вам нужно либо использовать обратный вызов, чтобы проверить выражение, либо вам нужно больше ограничить ваш макет. В любом случае это грязно и сложно. Я занимался этой проблемой до тех пор, пока я занимаюсь TDD. Я, наконец, бросил класс помощников, чтобы сделать это намного более выразительным и менее грязным. Здесь конечный результат (адаптированный к вашему примеру):
mockPeopleRepository
.Setup(x => x.Find(ThatHas.AnExpressionFor<Person>()
.ThatMatches(correctPerson)
.And().ThatDoesNotMatch(deletedPerson)
.Build()))
.Returns(_expectedListOfPeople);
Вот статья в блоге, в которой говорится об этом и дает исходный код: http://awkwardcoder.com/2013/04/24/constraining-mocks-with-expression-arguments/