c# - 如何模拟 Entity Framework 的 FromSqlRaw 方法?

标签 c# asp.net-core entity-framework-core xunit nsubstitute

我正在编写单元测试,需要模拟 Entity Framework 的 .FromSqlRaw 方法。在被测类中执行该方法时,会抛出以下异常:

System.InvalidOperationException: There is no method 'FromSqlOnQueryable' on type 'Microsoft.EntityFrameworkCore.RelationalQueryableExtensions' that matches the specified arguments.


以下是被测类:
public class PowerConsumptionRepository : IPowerConsumptionRepository
    {
        private readonly IDatabaseContext _databaseContext;
        private readonly IDateTimeHelper _dateTimeHelper;

        public PowerConsumptionRepository(IDatabaseContext databaseContext, IDateTimeHelper dateTimeHelper)
        {
            _databaseContext = databaseContext;
            _dateTimeHelper = dateTimeHelper;
        }
        public List<IntervalCategoryConsumptionModel> GetCurrentPowerConsumption(string siteId)
        {
            var currentDate = _dateTimeHelper
                .ConvertUtcToLocalDateTime(DateTime.UtcNow, ApplicationConstants.LocalTimeZone)
                .ToString("yyyy-MM-dd");
            var currentDateParameter = new SqlParameter("currentDate", currentDate);
            var measurements = _databaseContext.IntervalPowerConsumptions
                .FromSqlRaw(SqlQuery.CurrentIntervalPowerConsumption, currentDateParameter)
                .AsNoTracking()
                .ToList();
            return measurements;
        }
    }
单元测试:

    public class PowerConsumptionRepositoryTests
    {
        [Fact]
        public void TestTest()
        {
            var data = new List<IntervalCategoryConsumptionModel>
            {
                new IntervalCategoryConsumptionModel
                {
                    Id = 1,
                    Hvac = 10                    
                },
                new IntervalCategoryConsumptionModel
                {
                    Id = 1,
                    Hvac = 10
                }
            }.AsQueryable();
            var dateTimeHelper = Substitute.For<IDateTimeHelper>();
            dateTimeHelper.ConvertUtcToLocalDateTime(Arg.Any<DateTime>(), Arg.Any<string>()).Returns(DateTime.Now);
            var mockSet = Substitute.For<DbSet<IntervalCategoryConsumptionModel>, IQueryable<IntervalCategoryConsumptionModel>>();
            ((IQueryable<IntervalCategoryConsumptionModel>)mockSet).Provider.Returns(data.Provider);
            ((IQueryable<IntervalCategoryConsumptionModel>)mockSet).Expression.Returns(data.Expression);
            ((IQueryable<IntervalCategoryConsumptionModel>)mockSet).ElementType.Returns(data.ElementType);
            ((IQueryable<IntervalCategoryConsumptionModel>)mockSet).GetEnumerator().Returns(data.GetEnumerator());
            var context = Substitute.For<IDatabaseContext>();
            context.IntervalPowerConsumptions = (mockSet);
            var repo = new PowerConsumptionRepository(context, dateTimeHelper);
            var result = repo.GetCurrentPowerConsumption(Arg.Any<string>());
            result.Should().NotBeNull();
        }
    }

最佳答案

在我的场景中,我使用 FromSqlRaw在我的数据库中调用存储过程的方法。
对于 EntityFramework Core(3.1 版肯定可以很好地工作),我是这样做的:
将虚方法添加到您的 DbContext类(class):

public virtual IQueryable<TEntity> RunSql<TEntity>(string sql, params object[] parameters) where TEntity : class
{
    return this.Set<TEntity>().FromSqlRaw(sql, parameters);
}
只是一个简单的虚拟 静态包装器 FromSqlRaw ,所以你可以轻松地模拟它:
var dbMock = new Mock<YourContext>();
var tableContent = new List<YourTable>()
{
    new YourTable() { Id = 1, Name = "Foo" },
    new YourTable() { Id = 2, Name = "Bar" },
}.AsAsyncQueryable();
dbMock.Setup(_ => _.RunSql<YourTable>(It.IsAny<string>(), It.IsAny<object[]>())).Returns(tableContent );
调用我们的新 RunSql方法而不是 FromSqlRaw :
// Before
//var resut = dbContext.FromSqlRaw<YourTable>("SELECT * FROM public.stored_procedure({0}, {1})", 4, 5).ToListAsync();
// New
var result = dbContext.RunSql<YourTable>("SELECT * FROM public.stored_procedure({0}, {1})", 4, 5).ToListAsync();
最后但并非最不重要的是,您需要添加 AsAsyncQueryable()测试项目的扩展方法。它由用户@vladimir 在一个精彩的答案中提供 here :
public static class QueryableExtensions
{
    public static IQueryable<T> AsAsyncQueryable<T>(this IEnumerable<T> input)
    {
        return new NotInDbSet<T>( input );
    }

}

public class NotInDbSet< T > : IQueryable<T>, IAsyncEnumerable< T >, IEnumerable< T >, IEnumerable
{
    private readonly List< T > _innerCollection;
    public NotInDbSet( IEnumerable< T > innerCollection )
    {
        _innerCollection = innerCollection.ToList();
    }

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

    public IEnumerator< T > GetEnumerator()
    {
        return _innerCollection.GetEnumerator();
    }

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

    public class AsyncEnumerator : IAsyncEnumerator< T >
    {
        private readonly IEnumerator< T > _enumerator;
        public AsyncEnumerator( IEnumerator< T > enumerator )
        {
            _enumerator = enumerator;
        }

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

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

        public T Current => _enumerator.Current;
    }

    public Type ElementType => typeof( T );
    public Expression Expression => Expression.Empty();
    public IQueryProvider Provider => new EnumerableQuery<T>( Expression );
}

关于c# - 如何模拟 Entity Framework 的 FromSqlRaw 方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64302270/

相关文章:

c# - 在 .NET 和基于 COM 的组件之间传递时必须转换什么类型的数据?

c# - 执行从 C# 到 MySQL 的 INSERT 语句的正确方法

azure - 在 ASP.NET 5 MVC 6 中提供静态文件

c# - 如何使用集合

linq - EF Core 中孙子属性的 Include 与 thenInclude

c# - T 不包含 RowKey 的定义

c# - 相当于 C# BigInteger 的 SQL

c# - 如何将 ProcessModel 添加到 ASP.NET Core Web 应用程序

asp.net-core - 带有 GetView 的 IrazorViewEngine.FindView 找不到 View

c# - 将复合类型从 postgresql 映射到 ef core