【问题标题】:C#: Testing Entity Framework FromSql to ensure proper syntaxC#:测试实体框架 FromSql 以确保正确的语法
【发布时间】:2020-06-20 07:03:42
【问题描述】:

我写信是为了用 InMemory 数据库测试 FromSql 语句。我们正在尝试使用 Sqlite。

运行以下 Sql 无错误通过单元测试。

select * from dbo.Product

但是,这样做也会导致不正确的 sql 语法。想用不正确的 sql 语法使测试失败。如何正确测试FromSql?

没有错误来自语法错误的结果。

seledg24g5ct * frofhm dbo.Product

完整代码:

namespace Tests.Services
{
    public class ProductTest
    {
        private const string InMemoryConnectionString = "DataSource=:memory:";
        private SqliteConnection _connection;
        protected TestContext testContext;

        public ProductServiceTest()
        {
            _connection = new SqliteConnection(InMemoryConnectionString);
            _connection.Open();
            var options = new DbContextOptionsBuilder<TestContext>()
                    .UseSqlite(_connection)
                    .Options;
            testContext= new TestContext(options);
            testContext.Database.EnsureCreated();
        }


        [Fact]
        public async Task GetProductByIdShouldReturnResult()
        {
            var productList = testContext.Product
    .FromSql($"seledg24g5ct * frofhm dbo.Product");

            Assert.Equal(1, 1);
        }

使用 Net Core 3.1

【问题讨论】:

  • Assert.Equal(1, 1); - 总会通过 ;)
  • 是的,我如何确保语法会失败?通常它会在到达断言之前出错?
  • 想让测试因不正确的 sql 语法而失败 - 针对实际的 sql 数据库运行测试。
  • 我试图阻止本地主机,因为这是一个内存测试,sqlite 不强制执行吗? github.com/dotnet/efcore/issues/7212
  • “内存中”提供程序即使在 Sqlite 中也不是实际的 sql 引擎 - 如果您想测试原始 sql 查询,请针对实际的 sql 引擎运行它。

标签: c# entity-framework sqlite entity-framework-core


【解决方案1】:

这里有两点需要考虑。

首先,FromSql 方法只是在 EF Core 中使用原始 SQL 查询的一个小桥梁。调用该方法时不会对传递的 SQL 字符串进行任何验证/解析,除了查找参数占位符并将 db 参数与它们相关联。为了得到验证,它必须被执行

其次,为了支持对FromSql结果集的查询组合,该方法返回IQueryable&lt;T&gt;。这意味着它不会立即执行,而仅当/当枚举结果时才执行。当您在其上使用foreach 循环,或调用ToListToArray 或EF Core 特定的Load 扩展方法(类似于ToList,但不创建列表)时,可能会发生这种情况 - 相当于foreach 没有正文的循环,例如

foreach (var _ in query) { }

话虽如此,代码sn-p

var productList = testContext.Product
    .FromSql($"seledg24g5ct * frofhm dbo.Product");

基本上什么都不做,因此不会为无效 SQL 产生异常。您必须使用上述方法之一执行它,例如

productList.Load();

var productList = testContext.Product
    .FromSql($"seledg24g5ct * frofhm dbo.Product")
    .ToList();

并断言预期的异常。

有关详细信息,请参阅 EF Core 文档的 Raw SQL QueriesHow Queries Work 部分。

【讨论】:

    【解决方案2】:

    @ivan-stoev 回答了您的问题,即为什么您的 '.FromSql' 语句什么都不做 - 即查询从未真正实现。但是为了尝试增加一些额外的价值,我将分享我的单元测试设置,因为它对我很有效。当然,YMMV。

    1. 创建一个可重用的类来处理通用的内存数据库创建和轻松填充带有测试数据的表。注意:这需要 Nuget 包:
    • ServiceStack.OrmLite.Core
    • ServiceStack.OrmLite.Sqlite

    我正在使用 OrmLite,因为它允许通过提供非处置连接工厂来进行模拟和单元测试,我可以通过依赖注入巧妙地将其注入到测试类中:

    /// <summary>
        /// It is not possible to directly mock the Dapper commands i'm using to query the underlying database. There is a Nuget package called Moq.Dapper, but this approach doesnt need it.
        /// It is not possible to mock In-Memory properties of a .NET Core DbContext such as the IDbConnection - i.e. the bit we actually want for Dapper queries.
        /// for this reason, we need to use a different In-Memory database and load entities into it to query. Approach as per: https://mikhail.io/2016/02/unit-testing-dapper-repositories/
        /// </summary>
        public class TestInMemoryDatabase
        {
            private readonly OrmLiteConnectionFactory dbFactory =
                new OrmLiteConnectionFactory(":memory:", SqliteDialect.Provider);
    
            public IDbConnection OpenConnection() => this.dbFactory.OpenDbConnection();
    
            public void Insert<T>(IEnumerable<T> items)
            {
                using (var db = this.OpenConnection())
                {
                    db.CreateTableIfNotExist<T>();
                    foreach (var item in items)
                    {
                        db.Insert(item);
                    }
                }
            }
        }
    
    1. DbConnectionManager&lt;EFContext&gt;”类使用您已经创建的 EF 上下文为数据库连接提供包装器。这会从 EF 上下文中获取数据库连接并抽象出打开/关闭操作:
    public class DbConnectionManager<TContext> : IDbConnectionManager<TContext>, IDisposable
                where TContext : DbContext
            {
                private TContext _context;
        
                public DbConnectionManager(TContext context)
                {
                    _context = context;
                }
        
                public async Task<IDbConnection> GetDbConnectionFromContextAsync()
                {
                    var dbConnection = _context.Database.GetDbConnection();
        
                    if (dbConnection.State.Equals(ConnectionState.Closed))
                    {
                        await dbConnection.OpenAsync();
                    }
        
                    return dbConnection;
                }
        
                public void Dispose()
                {
                    var dbConnection = _context.Database.GetDbConnection();
        
                    if (dbConnection.State.Equals(ConnectionState.Open))
                    {
                        dbConnection.Close();
                    }
                }
            } 
    

    以上配套可注入接口:

    public interface IDbConnectionManager<TContext>
            where TContext : DbContext
        {
            Task<IDbConnection> GetDbConnectionFromContextAsync();
    
            void Dispose();
        }
    
    1. 在您的 .NET 项目启动类中,将此接口注册到内置 DI 容器(或您正在使用的任何容器):
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped(typeof(IDbConnectionManager<>), typeof(DbConnectionManager<>));
    }
    
    1. 现在我们的单元测试类如下所示:
    /// <summary>
        /// All tests to follow the naming convention: MethodName_StateUnderTest_ExpectedBehaviour
        /// </summary>
        [ExcludeFromCodeCoverage]
        public class ProductTests
        {
            //private static Mock<ILoggerAdapter<Db2DbViewAccess>> _logger;
            //private static Mock<IOptions<AppSettings>> _configuration;
            private readonly Mock<IDbConnectionManager<Db2Context>> _dbConnection;
    
            private readonly List<Product> _listProducts = new List<Product>
            {
                new Product
                {
                    Id = 1,
                    Name = "Product1"
                },
                new Product
                {
                    Id = 2,
                    Name = "Product2"
                },
                new Product
                {
                    Id = 3,
                    Name = "Product3"
                },
            };
    
            public ProductTests()
            {
                //_logger = new Mock<ILoggerAdapter<Db2DbViewAccess>>();
                //_configuration = new Mock<IOptions<AppSettings>>();
                _dbConnection = new Mock<IDbConnectionManager<Db2Context>>();
            }
    
            [Fact]
            public async Task GetProductAsync_ResultsFound_ReturnListOfAllProducts()
            {
                // Arrange
                // Using a SQL Lite in-memory database to test the DbContext. 
                var testInMemoryDatabase = new TestInMemoryDatabase();
                testInMemoryDatabase.Insert(_listProducts);
    
                _dbConnection.Setup(c => c.GetDbConnectionFromContextAsync())
                    .ReturnsAsync(testInMemoryDatabase.OpenConnection());
    
                //_configuration.Setup(x => x.Value).Returns(appSettings);
    
                var productAccess = new ProductAccess(_configuration.Object); //, _logger.Object, _dbConnection.Object);
    
                // Act
                var result = await productAccess.GetProductAsync("SELECT * FROM Product");
    
                // Assert
                result.Count.Should().Equals(_listProducts.Count);
            }
        }
    

    以上注意事项:

    • 您可以看到我正在测试一个“ProductAccess”数据访问类,该类包装了我的数据库调用,但这应该很容易为您的设置进行更改。我的 ProductAccess 类期望注入其他服务,例如日志记录和配置,但我已经在这个最小示例中注释掉了这些服务。
    • 注意内存数据库的设置并使用您要查询的实体的测试列表填充它现在只需 2 行(如果您想要相同的测试数据集,您甚至可以在 Test 类构造函数中只执行一次跨测试使用):

    var testInMemoryDatabase = new TestInMemoryDatabase(); testInMemoryDatabase.Insert(_listProducts);

    【讨论】:

      猜你喜欢
      • 2023-04-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-07
      • 2012-03-24
      • 1970-01-01
      相关资源
      最近更新 更多