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);
}
Ответы
Ответ 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/