简体   繁体   English

如何模拟实体框架的 FromSqlRaw 方法?

[英]How to mock Entity Framework's FromSqlRaw method?

I am writing a Unit Test and need to mock Entity Framework's .FromSqlRaw method.我正在编写单元测试,需要模拟实体框架的 .FromSqlRaw 方法。 When the method is executed in the class under test, it throws following exception:在被测类中执行该方法时,会抛出以下异常:

System.InvalidOperationException: There is no method 'FromSqlOnQueryable' on type 'Microsoft.EntityFrameworkCore.RelationalQueryableExtensions' that matches the specified arguments. System.InvalidOperationException:在类型“Microsoft.EntityFrameworkCore.RelationalQueryableExtensions”上没有与指定参数匹配的方法“FromSqlOnQueryable”。

Following is class under test:以下是被测类:

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;
        }
    }

Unit Test:单元测试:


    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();
        }
    }

With .FromSqlRaw you are sending raw sql query to the database engine.使用.FromSqlRaw您将原始 sql 查询发送到数据库引擎。
If you really want to test that your application ( .FromsqlRaw ) works as expected, test it against an actual database.如果您真的想测试您的应用程序 ( .FromsqlRaw ) 是否按预期工作,请针对实际数据库对其进行测试。

Yes it is slower, yes it requires running database with some test data - and yes it will provide you strong confidence that your application is working.是的,它更慢,是的,它需要使用一些测试数据运行数据库 - 是的,它会让您确信您的应用程序正在运行。

All other tests (mocked or in-memory or sqlite) will provide you false feeling of confidence.所有其他测试(模拟的、内存中的或 sqlite 的)都会给你错误的信心。

In my scenario I use FromSqlRaw method for invoke stored procedure in my database.在我的场景中,我使用FromSqlRaw方法调用数据库中的存储过程。 For EntityFramework Core (version 3.1 works well for sure) I do it in this way:对于 EntityFramework Core(3.1 版肯定可以很好地工作),我是这样做的:

Add virtual method to your DbContext class:将虚拟方法添加到您的DbContext类:

public virtual IQueryable<TEntity> RunSql<TEntity>(string sql, params object[] parameters) where TEntity : class
{
    return this.Set<TEntity>().FromSqlRaw(sql, parameters);
}

It's just a simple virtaul wraper from static FromSqlRaw , so you can easily mock it:它只是一个来自静态FromSqlRaw的简单虚拟包装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 );

Call our new RunSql method instead of FromSqlRaw :调用我们新的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();

Last, but not least, you need to add AsAsyncQueryable() extension method to your test project.最后但并非最不重要的是,您需要将AsAsyncQueryable()扩展方法添加到您的测试项目中。 It's provided by user @vladimir in a brilliant answer here :它由用户@vladimir 在这里提供了一个精彩的答案:

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 );
}

The in-memory provider can't do it as it's a relational operation.内存中的提供者不能这样做,因为它是一个关系操作。 Ignoring the philosophical side of it there are probably a couple of ways you could solve it.忽略它的哲学方面,可能有几种方法可以解决它。

  1. Mocking the query provider模拟查询提供者

Under the covers it's runs through the IQueryProvider.CreateQuery<T>(Expression expression) method so you can use a mocking framework to intercept the invocation and return what you want.IQueryProvider.CreateQuery<T>(Expression expression)它通过IQueryProvider.CreateQuery<T>(Expression expression)方法运行,因此您可以使用模拟框架来拦截调用并返回您想要的内容。 That's how EntityFrameworkCore.Testing (disclaimer I am the author) does it .这就是EntityFrameworkCore.Testing (免责声明我是作者)的 方式 This is how I unit test FromSql* invocations in my code.这就是我在代码中对FromSql*调用进行单元测试的FromSql*

  1. A better in-memory provider更好的内存提供程序

I haven't used it much but my understanding is a provider like SQLite may support it.我没有经常使用它,但我的理解是像 SQLite 这样的提供者可能会支持它。

To address the OP comments, WRT whether you should be using an in-memory provider/mocking the DbContext , we are in the realm of personal opinion.为了解决 OP 评论,WRT 是否应该使用内存中提供程序/ DbContext ,我们属于个人意见领域。 Mine is that I have no reservations using the in-memory provider, it's easy to use, reasonably fast and works well for many.我的一点是,我对使用内存中的提供程序没有任何保留,它易于使用、速度相当快并且对许多人来说效果很好。 I do agree that you shouldn't mock the DbContext , simply because it'd be really hard to do.我同意你不应该嘲笑DbContext ,仅仅因为它真的很难做到。 EntityFrameworkCore.Testing doesn't mock the DbContext per se, it wraps over an in-memory provider and uses popular mocking frameworks to provide support for things like FromSql* and ExecuteSql* . EntityFrameworkCore.Testing本身并不模拟DbContext ,它封装了一个内存中的提供程序并使用流行的模拟框架来提供对诸如FromSql*ExecuteSql*

I read the linked article by Jimmy Bogard (who I have the utmost respect for), however on this topic I don't agree on all points.我阅读了 Jimmy Bogard(我非常尊重他)的链接文章,但是在这个话题上,我并不同意所有观点。 On the rare occasion that I have raw SQL in my data access layer, it's generally to invoke a stored procedure or function which already has been tested/has tests outside of my SUT.在我的数据访问层中有原始 SQL 的极少数情况下,通常是调用已经在我的 SUT 之外测试/已经测试的存储过程或函数。 I generally treat them as a dependency;我通常将它们视为依赖项; I should be able to write my unit tests for my SUT with that dependency returning the values required to adequately test my SUT.我应该能够为我的 SUT 编写单元测试,该依赖项返回充分测试我的 SUT 所需的值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Entity Framework Core FromSqlRaw 模拟测试用例 - Entity Framework Core FromSqlRaw mock test cases 如何在带有实体框架4.0的MVC4中使用ViewModel模拟方法 - How to mock method with ViewModel in MVC4 with Entity Framework 4.0 如何在 Entity Framework Data First 中使用 FromSqlRaw 引用查找表中的字段? - How do I reference fields from a lookup table using FromSqlRaw in Entity Framework Data First? 我如何模拟实体框架的导航财产情报? - How Do I Mock Entity Framework's Navigational Property Intelligence? 如何使用 FromSqlRaw Entity Framework Core 3.1 从存储过程返回多个 SELECT 集 - How to return multiple SELECT sets from a stored procedure using FromSqlRaw Entity Framework Core 3.1 如何为实体框架模拟接口? - How to mock an interface for entity framework? C# 实体框架 fromsqlraw 查询,包含(in)而不是 where 子句 - C# entity framework fromsqlraw query with includes (in) instead of where clause 实体框架将小数点分隔符从传递的值更改为 FromSqlRaw - Entity Framework changes decimal separator from passed values to FromSqlRaw 如何使用IdentityDbContext模拟实体框架? - How to mock Entity Framework using IdentityDbContext? 如何模拟Entity Framework 6异步方法? - How to mock Entity Framework 6 Async methods?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM