@ivan-stoev 回答了您的问题,即为什么您的 '.FromSql' 语句什么都不做 - 即查询从未真正实现。但是为了尝试增加一些额外的价值,我将分享我的单元测试设置,因为它对我很有效。当然,YMMV。
- 创建一个可重用的类来处理通用的内存数据库创建和轻松填充带有测试数据的表。注意:这需要 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);
}
}
}
}
- “
DbConnectionManager<EFContext>”类使用您已经创建的 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();
}
- 在您的 .NET 项目启动类中,将此接口注册到内置 DI 容器(或您正在使用的任何容器):
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped(typeof(IDbConnectionManager<>), typeof(DbConnectionManager<>));
}
- 现在我们的单元测试类如下所示:
/// <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);