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

Издевательские классы, реализующие 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());

Но это не то, что я хочу сделать =)

4b9b3361

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

Я надеюсь, что дополнительные комментарии здесь не требуются.