单元文本模拟 dbContext

unit text mock dbContext

我尝试在 returns 可枚举的存储库上进行单一测试。但是我有下一个错误:

  System.AggregateException : One or more errors occurred. (The source IQueryable doesn't implement IAsyncEnumerable<myNamespace.DTO.UserDTO>. Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations.)
    ---- System.InvalidOperationException : The source IQueryable doesn't implement IAsyncEnumerable<myNamespace.DTO.UserDTO>. Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations.
 

这是我的单元测试:

            //Arrange
            var mockSet = Substitute.For<DbSet<User>, IQueryable<User>, IDbAsyncEnumerable<User>>();

            ((IDbAsyncEnumerable<User>)mockSet).GetAsyncEnumerator()
               .Returns(new TestDbAsyncEnumerator<User>(GetUserList().AsQueryable().GetEnumerator()));
            ((IQueryable<User>)mockSet).Provider.Returns(new TestDbAsyncQueryProvider<User>(GetUserList().AsQueryable().Provider));
            ((IQueryable<User>)mockSet).Expression.Returns(GetUserList().AsQueryable().Expression);
            ((IQueryable<User>)mockSet).ElementType.Returns(GetUserList().AsQueryable().ElementType);
            ((IQueryable<User>)mockSet).GetEnumerator().Returns(GetUserList().AsQueryable().GetEnumerator());

            var mockContext = Substitute.For<IMyContext>();
            mockContext.Users.Returns(mockSet);

            //Act 
            CancellationToken cancellationToken = new CancellationToken();
            UserRepository userRepository = new UserRepository(mockContext);
            var users = userRepository.GetListAsync(cancellationToken).Result;

            //Assert  
            Assert.NotNull(users);

我想测试我的仓库:

public async Task<IEnumerable<UserDto>> GetListAsync(CancellationToken cancellationToken)
{
    return await _myContext.Users.Select(u => new UserDto
    {
        Id = u.Id,
        FistName = u.FistName ,
        LastName = u.LastName 
    }).ToListAsync(cancellationToken);
}

有什么问题?

正如 OP 评论中所述,您所指的 doco 适用于 EF,而不是 EFCore。您需要实现一组不同的接口。

通常的建议是避免模拟 DbContext 但是在这种情况下,您可能需要这样做,因为内存提供程序不支持异步操作。我不确定 SQLite 是否支持它们。 EntityFrameworkCore.Testing 应该处理这种情况(免责声明,我是作者),但您需要使用上下文实现而不是接口。

实现此功能的最常见方法是创建异步接口的实现,其方式与 EF doco 相同,但适用于 EFCore。您会发现大多数 EFCore 模拟库都会这样做:

public class TestAsyncEnumerable<T> : IAsyncEnumerable<T>, IOrderedQueryable<T>
{
    private readonly IEnumerable<T> _enumerable;
    private readonly IQueryable<T> _queryable;

    public TestAsyncEnumerable(IEnumerable<T> enumerable)
    {
        _enumerable = enumerable;

        _queryable = _enumerable.AsQueryable();

        ElementType = _queryable.ElementType;
        Expression = _queryable.Expression;
        Provider = new TestAsyncQueryProvider<T>(_queryable);
    }

    public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken())
    {
        return new TestAsyncEnumerator<T>(_queryable);
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        return _enumerable.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _enumerable.GetEnumerator();
    }

    public Type ElementType { get; }
    public Expression Expression { get; }
    public IQueryProvider Provider { get; }
}

public class TestAsyncEnumerator<T> : IAsyncEnumerator<T>
{
    private readonly IEnumerator<T> _enumerator;

    public TestAsyncEnumerator(IEnumerable<T> enumerable)
    {
        _enumerator = enumerable.GetEnumerator();
    }

    public ValueTask DisposeAsync()
    {
        return new ValueTask();
    }

    public ValueTask<bool> MoveNextAsync()
    {
        return new ValueTask<bool>(_enumerator.MoveNext());
    }

    public T Current => _enumerator.Current;
}

public class TestAsyncQueryProvider<T> : IAsyncQueryProvider
{
    public TestAsyncQueryProvider(IQueryable<T> source)
    {
        Source = source;
    }

    private IQueryable<T> Source { get; }

    public IQueryable CreateQuery(Expression expression)
    {
        throw new NotImplementedException();
    }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        return new TestAsyncEnumerable<TElement>(Source.Provider.CreateQuery<TElement>(expression));
    }

    public object Execute(Expression expression)
    {
        throw new NotImplementedException();
    }

    public TResult Execute<TResult>(Expression expression)
    {
        throw new NotImplementedException();
    }

    public TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken = new CancellationToken())
    {
        throw new NotImplementedException();
    }
}

这不是一个完整的实现,只是解决 OP 案例所需要的。重要的一点是这一行:

public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
    return new TestAsyncEnumerable<TElement>(Source.Provider.CreateQuery<TElement>(expression));
}

这将允许投影与异步操作一起工作。

工作示例:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using AutoFixture;
using KellermanSoftware.CompareNetObjects;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query.Internal;
using NSubstitute;
using NUnit.Framework;

namespace Question62783423
{
    public class Tests
    {
        [Test]
        public void Test1()
        {
            var fixture = new Fixture();

            var users = new TestAsyncEnumerable<User>(fixture.CreateMany<User>());

            //Arrange
            var mockSet = Substitute.For<DbSet<User>, IQueryable<User>, IAsyncEnumerable<User>>();

            ((IAsyncEnumerable<User>) mockSet).GetAsyncEnumerator().Returns(users.GetAsyncEnumerator());
            ((IQueryable<User>) mockSet).Provider.Returns(users.Provider);
            ((IQueryable<User>) mockSet).Expression.Returns(users.Expression);
            ((IQueryable<User>) mockSet).ElementType.Returns(users.ElementType);
            ((IQueryable<User>) mockSet).GetEnumerator().Returns(((IQueryable<User>) users).GetEnumerator());

            var mockContext = Substitute.For<IMyContext>();
            mockContext.Users.Returns(mockSet);

            //Act 
            var cancellationToken = new CancellationToken();
            var userRepository = new UserRepository(mockContext);
            var result1 = userRepository.GetListAsync(cancellationToken).Result;
            var result2 = userRepository.GetListAsync(cancellationToken).Result;

            var comparer = new CompareLogic();
            comparer.Config.IgnoreCollectionOrder = true;
            comparer.Config.IgnoreObjectTypes = true;
            var comparisonResult1 = comparer.Compare(users, result1);
            var comparisonResult2 = comparer.Compare(users, result2);

            Assert.That(comparisonResult1.Differences.Any(), Is.False);
            Assert.That(comparisonResult2.Differences.Any(), Is.False);
        }
    }
}

public class TestAsyncEnumerable<T> : IAsyncEnumerable<T>, IOrderedQueryable<T>
{
    private readonly IEnumerable<T> _enumerable;
    private readonly IQueryable<T> _queryable;

    public TestAsyncEnumerable(IEnumerable<T> enumerable)
    {
        _enumerable = enumerable;

        _queryable = _enumerable.AsQueryable();

        ElementType = _queryable.ElementType;
        Expression = _queryable.Expression;
        Provider = new TestAsyncQueryProvider<T>(_queryable);
    }

    public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken())
    {
        return new TestAsyncEnumerator<T>(_queryable);
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        return _enumerable.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _enumerable.GetEnumerator();
    }

    public Type ElementType { get; }
    public Expression Expression { get; }
    public IQueryProvider Provider { get; }
}

public class TestAsyncEnumerator<T> : IAsyncEnumerator<T>
{
    private readonly IEnumerator<T> _enumerator;

    public TestAsyncEnumerator(IEnumerable<T> enumerable)
    {
        _enumerator = enumerable.GetEnumerator();
    }

    public ValueTask DisposeAsync()
    {
        return new ValueTask();
    }

    public ValueTask<bool> MoveNextAsync()
    {
        return new ValueTask<bool>(_enumerator.MoveNext());
    }

    public T Current => _enumerator.Current;
}

public class TestAsyncQueryProvider<T> : IAsyncQueryProvider
{
    public TestAsyncQueryProvider(IQueryable<T> source)
    {
        Source = source;
    }

    private IQueryable<T> Source { get; }

    public IQueryable CreateQuery(Expression expression)
    {
        throw new NotImplementedException();
    }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        return new TestAsyncEnumerable<TElement>(Source.Provider.CreateQuery<TElement>(expression));
    }

    public object Execute(Expression expression)
    {
        throw new NotImplementedException();
    }

    public TResult Execute<TResult>(Expression expression)
    {
        throw new NotImplementedException();
    }

    public TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken = new CancellationToken())
    {
        throw new NotImplementedException();
    }
}

public class User
{
    public Guid Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class UserDto
{
    public Guid Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public interface IMyContext
{
    DbSet<User> Users { get; set; }
}

public class UserRepository
{
    private readonly IMyContext _myContext;

    public UserRepository(IMyContext myContext)
    {
        _myContext = myContext;
    }

    public async Task<IEnumerable<UserDto>> GetListAsync(CancellationToken cancellationToken)
    {
        return await _myContext.Users.Select(u => new UserDto { Id = u.Id, FirstName = u.FirstName, LastName = u.LastName }).ToListAsync(cancellationToken);
    }
}