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/