我知道有几个类似于我的问题。
但我不认为上述两个问题都有明确的答案符合我的要求。
现在我开发了一个新的WebAPI项目,并在WebAPI项目和DataAccess技术之间进行了划分。我没有问题测试Controller for WebAPI因为我可以模拟数据访问类。
但对于DataAccess类来说,这是一个不同的故事,因为我在其中使用Dapper内联查询,我有点混淆我如何使用单元测试来测试它。我问了一些朋友,他们更喜欢做集成测试而不是单元测试。
我想知道的是,是否可以对使用Dapper和Inline查询的DataAccess类进行单元测试。
假设我有一个这样的类(这是一个通用的存储库类,因为很多代码都有类似的查询区别于表名和字段)
public abstract class Repository<T> : SyncTwoWayXI, IRepository<T> where T : IDatabaseTable
{
public virtual IResult<T> GetItem(String accountName, long id)
{
if (id <= 0) return null;
SqlBuilder builder = new SqlBuilder();
var query = builder.AddTemplate("SELECT /**select**/ /**from**/ /**where**/");
builder.Select(string.Join(",", typeof(T).GetProperties().Where(p => p.CustomAttributes.All(a => a.AttributeType != typeof(SqlMapperExtensions.DapperIgnore))).Select(p => p.Name)));
builder.From(typeof(T).Name);
builder.Where("id = @id", new { id });
builder.Where("accountID = @accountID", new { accountID = accountName });
builder.Where("state != 'DELETED'");
var result = new Result<T>();
var queryResult = sqlConn.Query<T>(query.RawSql, query.Parameters);
if (queryResult == null || !queryResult.Any())
{
result.Message = "No Data Found";
return result;
}
result = new Result<T>(queryResult.ElementAt(0));
return result;
}
// Code for Create, Update and Delete
}
上面代码的实现就像
public class ProductIndex: IDatabaseTable
{
[SqlMapperExtensions.DapperKey]
public Int64 id { get; set; }
public string accountID { get; set; }
public string userID { get; set; }
public string deviceID { get; set; }
public string deviceName { get; set; }
public Int64 transactionID { get; set; }
public string state { get; set; }
public DateTime lastUpdated { get; set; }
public string code { get; set; }
public string description { get; set; }
public float rate { get; set; }
public string taxable { get; set; }
public float cost { get; set; }
public string category { get; set; }
public int? type { get; set; }
}
public class ProductsRepository : Repository<ProductIndex>
{
// ..override Create, Update, Delete method
}
这是我们的方法:
首先,你需要在IDbConnection
之上有一个抽象来模拟它:
public interface IDatabaseConnectionFactory
{
IDbConnection GetConnection();
}
您的存储库将从此工厂获取连接并对其执行Dapper
查询:
public class ProductRepository
{
private readonly IDatabaseConnectionFactory connectionFactory;
public ProductRepository(IDatabaseConnectionFactory connectionFactory)
{
this.connectionFactory = connectionFactory;
}
public Task<IEnumerable<Product>> GetAll()
{
return this.connectionFactory.GetConnection().QueryAsync<Product>(
"select * from Product");
}
}
您的测试将创建一个包含一些示例行的内存数据库,并检查存储库如何检索它们:
[Test]
public async Task QueryTest()
{
// Arrange
var products = new List<Product>
{
new Product { ... },
new Product { ... }
};
var db = new InMemoryDatabase();
db.Insert(products);
connectionFactoryMock.Setup(c => c.GetConnection()).Returns(db.OpenConnection());
// Act
var result = await new ProductRepository(connectionFactoryMock.Object).GetAll();
// Assert
result.ShouldBeEquivalentTo(products);
}
我想有多种方法可以实现这样的内存数据库;我们在SQLite
数据库上使用了OrmLite
:
public class InMemoryDatabase
{
private readonly OrmLiteConnectionFactory dbFactory = new OrmLiteConnectionFactory(":memory:", SqliteOrmLiteDialectProvider.Instance);
public IDbConnection OpenConnection() => this.dbFactory.OpenDbConnection();
public void Insert<T>(IEnumerable<T> items)
{
using (var db = this.OpenConnection())
{
db.CreateTableIfNotExists<T>();
foreach (var item in items)
{
db.Insert(item);
}
}
}
}