在我的 C# 项目中,我有一个末尾带有 .ToList();
的查询:
public List<Product> ListAllProducts()
{
List<Product> products = db.Products
.Where(...)
.Select(p => new Product()
{
ProductId = p.ProductId,
...
})
.ToList();
return products;
}
时不时地,我会收到EntityException:“底层提供程序在打开时失败。”
错误。我根据我在 SO 上找到的答案做了以下解决方法
public List<Product> ListAllProducts()
{
try
{
// When accessing a lot of Database Rows the following error might occur:
// EntityException: "The underlying provider failed on Open." With an inner TimeoutExpiredException.
// So, we use a new db so it's less likely to occur
using(MyDbContext usingDb = new MyDbContext())
{
// and also set the CommandTimeout to 2 min, instead of the default 30 sec, just in case
((IObjectContextAdapter)usingDb).ObjectContext.CommandTimeout = 120;
List<Product> products = usingDb.Products
.Where(...)
.Select(p => new Product()
{
ProductId = p.ProductId,
...
})
.ToList();
return products;
}
}
// and also have a try-catch in case the error stil occurres,
// since we don't want the entire website to crash
catch (EntityException ex)
{
Console.WriteLine("An error has occured: " + ex.StackTrace);
return new List<Product>();
}
}
到目前为止,它似乎有效,我每次都会收到数据(以防万一我还添加了 try-catch)。
现在我正在进行单元测试,但遇到了一个问题。我的 UnitTest 中有一个 MockDbContext
,但由于我使用的是非 Mock 版本的 using(...)
,所以我的 UnitTest 失败了。我如何使用 MockDbContext 而不是默认的来测试这一点?
编辑:
我刚刚遇到this post about UnitTesting Mock DbContext 。所以在我看来,我确实需要将它一直传递到我的方法,或者只是不使用 using(MyDbContext usingDb = new MyDbContext())
而只是暂时增加 CommandTimeout并使用try-catch。真的没有别的办法了吗?或者,对于我遇到的错误,是否有更好的修复/解决方法,而无需使用 using(MyDbContext usingDb = new MyDbContext())
?
编辑2:
为了让我的情况更清楚:
我有 MyDbContext 类和 IMyDbContext 接口(interface)。
我的每个 Controller 中都有以下内容:
public class ProductController : Controller
{
private IMyDbContext _db;
public ProductController(IMyDbContext db)
{
this._db = db;
}
... // Methods that use this "_db"
public List<Product> ListAllProducts()
{
try
{
using(MyDbContext usingDb = new MyDbContext())
{
... // Query that uses "usingDb"
}
}
catch(...)
{
...
}
}
}
在我的正常代码中,我创建如下实例:
ProductController controller = new ProductController(new MyDbContext());
在我的 UnitTest 代码中,我创建了如下实例:
ProductController controller = new ProductController(this.GetTestDb());
// With GetTestDb something like this:
private IMyDbContext GetTestDb()
{
var memoryProducts = new Product { ... };
...
var mockDB = new Mock<FakeMyDbContext>();
mockDb.Setup(m => m.Products).Returns(memoryProducts);
...
return mockDB.Object;
}
除了 ListAll 方法中的 using(MyDbContext usingDb = new MyDbContext())
部分之外,在我作为 UnitTest 的正常项目中,一切都运行良好。
最佳答案
虽然希望分解代码以促进测试,但仅在测试中执行的代码分支并没有帮助。实际上,它的作用是将测试环境配置代码放入您的实时代码中。
您需要的是创建 DbContext
的更多抽象。您应该传入一个构造DbContext
的委托(delegate):
public List<Product> ListAllProducts(Func<DbContext> dbFunc)
{
using(MyDbContext usingDb = dbFunc())
{
// ...
}
}
现在您不再传递繁重的 DbContext
对象,您可以将方法参数重构到类构造函数中。
public class ProductDAL
{
private Func<DbContext> _dbFunc;
public ProductDAL(Func<DbContext> dbFunc)
{
_dbFunc = dbFunc;
}
public List<Product> ListAllProducts()
{
using(MyDbContext usingDb = _dbFunc())
{
// ...
}
}
}
最重要的是,您现在可以控制要使用的 DbContext
,包括从配置文件中提取的连接字符串,一直在 IOC 容器中,而无需在任何地方担心它代码中的其他内容。
关于c# - 在 UnitTest Mock-DbContext 上使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25061160/