在我的C#项目中,我有一个末尾带有.ToList();
的查询:
public List<Product> ListAllProducts()
{
List<Product> products = db.Products
.Where(...)
.Select(p => new Product()
{
ProductId = p.ProductId,
...
})
.ToList();
return products;
}
有时,我会收到
EntityException: "The underlying provider failed on Open."
错误。我根据在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
,但是由于我使用的是非模拟版本的using(...)
,所以我的UnitTests失败。我如何使用MockDbContext而不是默认值进行测试?编辑:
我刚遇到this post about UnitTesting Mock DbContext。因此在我看来,我确实确实需要将其完全传递给我的方法,或者只是不使用
using(MyDbContext usingDb = new MyDbContext())
而是仅临时增加CommandTimeout并使用try-catch。真的没有其他办法吗?还是在没有using(MyDbContext usingDb = new MyDbContext())
的情况下针对我遇到的错误是否有更好的修复/解决方法?编辑2:
为了使我的情况更加清楚:
我有MyDbContext类和IMyDbContext接口。
每个控制器中都包含以下内容:
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
的委托:
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/