Издевательские классы, реализующие IQueryable с Moq
Я провел вечер, пытаясь издеваться над объектом, который реализует IQueryable:
public interface IRepo<T> : IQueryable<T>
{
}
Лучшее, что я мог придумать, это что-то вроде этого:
var items = new Item[] {}.AsQueryable();
var repo = new Mock<IRepo>();
repo.Setup(r => r.GetEnumerator()).Returns(items.GetEnumerator());
repo.Setup(r => r.Provider).Returns(items.Provider);
repo.Setup(r => r.ElementType).Returns(items.ElementType);
repo.Setup(r => r.Expression).Returns(items.Expression);
Есть ли более краткий способ сделать то же самое? Было бы проще разоблачить свойство/метод в IRepo, который возвращает IQueryable и просто макет:
repo.Setup(r => r.GetItems()).Returns(new Items[]{ }.AsQueryable());
Но это не то, что я хочу сделать =)
Ответы
Ответ 1
Это ничего нового, просто более чистый способ сделать это. У меня также есть репозитории, в которых сам репозиторий также является IQueryable, поэтому мне нужно то же самое. В основном я просто помещаю ваш код в метод расширения, подобный этому, на уровне корня моего тестового проекта, чтобы сделать его доступным для всех тестов:
public static class MockExtensions
{
public static void SetupIQueryable<T>(this Mock<T> mock, IQueryable queryable)
where T: class, IQueryable
{
mock.Setup(r => r.GetEnumerator()).Returns(queryable.GetEnumerator());
mock.Setup(r => r.Provider).Returns(queryable.Provider);
mock.Setup(r => r.ElementType).Returns(queryable.ElementType);
mock.Setup(r => r.Expression).Returns(queryable.Expression);
}
}
Это в основном просто предлагает повторное использование, поскольку вы, вероятно, захотите сделать это в нескольких тестах, и в каждом тесте он делает намерение понятным и беспорядок минимален.:)
Ответ 2
Ответ на Rune является удивительным и спас меня, когда мы выяснили, как сделать то же самое.
Малый gotcha заключается в том, что если вы дважды вызываете некоторые методы расширения IQueryable в своем IQueryable (например, ToList()), то во второй раз вы не получите никаких результатов. Это потому, что перечислитель заканчивается и требует сброса.
Используя Rhinomocks, я изменил реализацию для GetEnumerator на:
mock.Stub(r => r.GetEnumerator()).Do((Func<IEnumerator<T>>) (() => {
var enumerator = queryable.GetEnumerator();
enumerator.Reset();
return enumerator;
}));
Надеюсь, что это кому-то поможет.
Ответ 3
Мне нравится ответ Рун. Вот общая версия IQueryable:
public static void SetupIQueryable<TRepository, TEntity>(this Mock<TRepository> mock, IQueryable<TEntity> queryable)
where TRepository : class, IQueryable<TEntity>
{
mock.Setup(r => r.GetEnumerator()).Returns(queryable.GetEnumerator());
mock.Setup(r => r.Provider).Returns(queryable.Provider);
mock.Setup(r => r.ElementType).Returns(queryable.ElementType);
mock.Setup(r => r.Expression).Returns(queryable.Expression);
}
Ответ 4
Я думаю, что о лучшем вы можете сделать с Moq. Я думаю, что более читаемым вариантом было бы свернуть собственный FakeRepo<T>
, который происходит от System.Linq.EnumerableQuery<T>
:
public class FakeRepo<T> : EnumerableQuery<T>, IRepo<T>
{
public FakeRepo(IEnumerable<T> items) : base(items) { }
}
Обновление: Вы могли бы снять это, высмеивая EnumerableQuery<T>
, затем используя As<T>()
:
var items = new Item[0];
var repo = new Mock<EnumerableQuery<Item>(items).As<IRepo>();
Ответ 5
У меня была такая же проблема. Я исправил это, изменив эту строку
mock.Setup(r => r.GetEnumerator()).Returns(queryable.GetEnumerator());
к
mock.Setup(r => r.GetEnumerator()).Returns(queryable.GetEnumerator);
Я надеюсь, что дополнительные комментарии здесь не требуются.