Подтвердить что ты не робот

Moq.Mock<t> - как настроить метод, который принимает выражение

Я проверяю интерфейс моего репозитория и не уверен, как настроить метод, который принимает выражение и возвращает объект? Я использую Moq и NUnit.

Интерфейс:

public interface IReadOnlyRepository : IDisposable
{
    IQueryable<T> All<T>() where T : class;
    T Single<T>(Expression<Func<T, bool>> expression) where T : class;
}

Тест с IQueryable уже настроен, но вы не знаете, как настроить T Single:

private Moq.Mock<IReadOnlyRepository> _mockRepos;
private AdminController _controller;
[SetUp]
public void SetUp()
{
    var allPages = new List<Page>();
    for (var i = 0; i < 10; i++)
    {
        allPages.Add(new Page { Id = i, Title = "Page Title " + i, Slug = "Page-Title-" + i, Content = "Page " + i + " on page content." });
    }
    _mockRepos = new Moq.Mock<IReadOnlyRepository>();
    _mockRepos.Setup(x => x.All<Page>()).Returns(allPages.AsQueryable());
    //Not sure what to do here???
    _mockRepos.Setup(x => x.Single<Page>()
    //----
    _controller = new AdminController(_mockRepos.Object);
}
4b9b3361

Ответ 1

Вы можете настроить его следующим образом:

_mockRepos.Setup(x => x.Single<Page>(It.IsAny<Expression<Func<Page, bool>>>()))//.Returns etc...;

Однако вы сталкиваетесь с одним из недостатков Moq. Вы бы хотели вместо этого использовать фактическое выражение вместо It.IsAny, но Moq не поддерживает настройку методов, которые принимают выражения с определенными выражениями (это сложная функция для реализации). Трудность состоит в том, чтобы выяснить, эквивалентны ли два выражения.

Таким образом, в вашем тесте вы можете пройти через любой Expression<Func<Page,bool>>, и он вернет все, что вы настроили макет, чтобы вернуться. Значение теста немного разбавлено.

Ответ 2

Попросите вызов .Returns вернуть результат выражения против вашей переменной allPages.

_mockRepos.Setup(x => x.Single<Page>(It.IsAny<Expression<Func<Page, bool>>>()))
    .Returns( (Expression<Func<Page, bool>> predicate) => allPages.Where(predicate) );

Ответ 3

Я обнаружил, что It.Is<T> следует использовать вместо It.IsAny<T> для получения более точных результатов.

Page expectedPage = new Page {Id = 12, Title = "Some Title"};
_mockRepos.Setup(x => x.Single<Page>(It.Is<Expression<Func<Page, bool>>>(u => u.Compile().Invoke(expectedPage))))
                       .Returns(() => expectedPage);

Ответ 4

Использование 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/