DbSet mock, никаких результатов при вызове ToList во-вторых

Я пытаюсь высмеять DbContext и DbSet. Это работает для моих предыдущих модульных тестов, но проблема возникает, когда мой код дважды вызывал метод ToList на DbSet.

Вначале dbSet.ToList() возвращает посмеянные результаты. Второй возвращает 0 элементов;

       var queryableData = new List<string>{ "a", "b", "c" }.AsQueryable();

        var mockDbSet = new Mock<DbSet<string>>();
        var q = mockDbSet.As<IQueryable<string>>();
        q.Setup(m => m.Provider).Returns(queryableData.Provider);
        q.Setup(m => m.Expression).Returns(queryableData.Expression);
        q.Setup(m => m.ElementType).Returns(queryableData.ElementType);
        q.Setup(m => m.GetEnumerator()).Returns(queryableData.GetEnumerator());

        DbSet<string> dbset = mockDbSet.Object;
        IQueryable<string> query = dbset;

        //RESULTS: abc
        var a1 = dbset.ToList();
        foreach (var a in a1)
            Console.Write(a);

        //NO RESULTS
        var a2 = dbset.ToList();
        foreach (var a in a2)
            Console.Write(a);

Ответы

Ответ 1

Вы возвращаете тот же самый экземпляр перечисления при каждом вызове GetEnumerator. Когда он перечисляется один раз, это делается, EF не вызывает свой метод Reset, а запрашивает новый счетчик.

Но вы возвращаете тот, который только что дал все элементы и больше не дает.

Вместо этого верните функцию, которая возвращает перечислитель, который будет возвращать новый счетчик каждый раз, когда вы его попросите.

 q.Setup(m => m.GetEnumerator()).Returns( () => queryableData.GetEnumerator() );

Ответ 2

Я просто хотел добавить в Wiktor Zychla ответ на мою небольшую часть. Если кто-то ищет версию этого макета Async (из этого урока: http://msdn.microsoft.com/en-us/data/dn314429.aspx#async), то это моя модификация класса TestDbAsyncEnumerator<T>:

internal class TestDbAsyncEnumerator<T> : IDbAsyncEnumerator<T>
{
    private readonly IEnumerator<T> _inner;

    public TestDbAsyncEnumerator(IEnumerator<T> inner)
    {
        _inner = inner;
    }

    public TestDbAsyncEnumerator(Func<IEnumerator<T>> valueFunction)
    {
        _inner = valueFunction();
    }

    public void Dispose()
    {
        _inner.Dispose();
    }

    public Task<bool> MoveNextAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult(_inner.MoveNext());
    }

    public T Current
    {
        get { return _inner.Current; }
    }

    object IDbAsyncEnumerator.Current
    {
        get { return Current; }
    }
}

Затем, как и Wiktor, вам нужно настроить его с помощью делегата, поэтому в случае async это будет так:

mockSet.As<IDbAsyncEnumerable<Blog>>()
            .Setup(m => m.GetAsyncEnumerator())
            .Returns(new TestDbAsyncEnumerator<Blog>(() => data.GetEnumerator()));

Если кто-то хочет источник для этого, то здесь вы идете: https://github.com/kelostrada/EntityFrameworkWithMock.Test

Ответ 3

Я не могу комментировать запись в Wiktor, поскольку у меня недостаточно репутации, но я хотел бы добавить немного дополнительного ответа Wiktor.

Код

mockSet.Setup((m => m.GetEnumerator()).Returns(() => data.GetEnumerator())

Не получится, если вы дадите ему нулевой аргумент лямбда, как в примере (по крайней мере, для версии, которую я использую). Передача аргумента, который никогда не используется, позволит ему выполнить требование подписи.

mockSet.Setup((m => m.GetEnumerator()).Returns(x => data.GetEnumerator())

Будет компилироваться и работает, как ожидалось.

И это также относится и к ответу Келу (хотя он добавил лямбда в неправильном месте.

mockSet.As<IDbAsyncEnumerable<Blog>>()
        .Setup(m => m.GetAsyncEnumerator())
        .Returns(x => new TestDbAsyncEnumerator<Blog>(data.GetEnumerator()));

Не пытаясь найти ответы здесь, я просто добавляю, потому что сам сделал эти ошибки:)

Ответ 4

Если вы положили предложение "Where" перед вызовом .ToList(), данные должны оставаться в наличии.

var a1 = dbset.Where(m => m != null).ToList();

var a2 = dbset.Where(m => m != null).ToList();