我正在使用EF Core访问SQL Server数据库的ASP.NET Core 3.1中编写API。我在API中有一个函数,需要使用几个输入参数和一个输出参数来调用存储过程。下面是此功能的简化版本。
我正在将DbContext
与.UseInMemoryDatabase()
一起用于其他测试,但是内存数据库不能与存储过程一起使用。
(此解决方案首先是数据库,而不是代码。如有必要,可以更改存储过程,但是如果不需要的话,它会更好。我可以更改C#函数以将存储过程称为a但如果有帮助的话,可以采取其他方法。)
如何对该功能进行单元测试?
public class MyFoo : IFoo
{
public ApplicationDbContext DbContext { get; }
public MyFoo(ApplicationDbContext dbContext)
{
DbContext = dbContext;
}
public async Task<bool> GetMyStoredProcResult(string val1, string val2, string val3, string val4, string val5)
{
// input validation removed for brevity
var p1 = new SqlParameter
{
ParameterName = "p1",
DbType = System.Data.DbType.String,
Direction = System.Data.ParameterDirection.Input,
Value = val1
};
// p2 - p5 removed for brevity
var resultParam = new SqlParameter
{
ParameterName = "Result",
DbType = System.Data.DbType.Boolean,
Direction = System.Data.ParameterDirection.Output
};
var sql = "EXEC sp_MyProcedure @p1, @p2, @p3, @p4, @p5, @Result OUTPUT";
_ = await DbContext.Database.ExecuteSqlRawAsync(sql, p1, p2, p3, p4, p5, resultParam);
return (bool)resultParam.Value;
}
}
在单元测试中,您可以假设其他部分和外部调用始终执行正常。因此,您可以模拟这样的呼叫。一种方法是通过将过程调用移入具有特定实现的接口来实现一些策略
interface IProcedureExecutor
{
void MyProcedure();
}
class DefaultProcedureExecutor: IProcedureExecutor
{
public void MyProcedure()
{
//call procedure in database
}
}
class MockedProcedureExecutor: IProcedureExecutor
{
public void MyProcedure()
{
//do some direct data operations with in memory database
}
}
然后在IProcedureExecutor
中传递MyFoo
,并在单元测试期间将实例替换为模拟的实例。
这只是一个例子。您也可以像这样模拟任何部分
我的最终解决方案基于Stas Petrov给出的答案。我使用一个接口包装了对DbContext.Database.ExecuteSqlRawAsync()
的调用,该接口具有在Startup.ConfigureServices()
中添加到DI的类。
我创建了以下接口和类:
public interface IStoredProcedureExecutor { public Task<int> ExecuteSqlRawAsync(string sql, params object[] parameters); } public class StoredProcedureExecutor : IStoredProcedureExecutor { public ApplicationDbContext DbContext { get; } public StoredProcedureExecutor(ApplicationDbContext dbContext) { DbContext = dbContext; } public Task<int> ExecuteSqlRawAsync(string sql, params object[] parameters) { return DbContext.Database.ExecuteSqlRawAsync(sql, parameters); } }
在问题代码中,我替换了此调用:
_ = await DbContext.Database.ExecuteSqlRawAsync(sql, p1, p2, p3, p4, p5, resultParam);
使用此:
_ = await StoredProcedureExecutor.ExecuteSqlRawAsync(sql, p1, p2, p3, p4, p5, resultParam);
然后在测试代码中,我创建了一个实例化的类,设置了合适的
ReturnValue
,然后将其插入我正在测试的类中,而不是StoredProcedureExecutor
:
class TestStoredProcedureExecutor : IStoredProcedureExecutor
{
public bool ReturnValue { get; set; }
public Task<int> ExecuteSqlRawAsync(string sql, params object[] parameters)
{
foreach (var param in parameters)
{
var p = (SqlParameter)param;
if (p.Direction == System.Data.ParameterDirection.Output) p.Value = ReturnValue;
}
return Task.FromResult(0);
}
}