C# 在UnitTest模拟DbContext上使用
在我的C#项目中,我有一个带有C# 在UnitTest模拟DbContext上使用,c#,asp.net-mvc,entity-framework,unit-testing,mocking,C#,Asp.net Mvc,Entity Framework,Unit Testing,Mocking,在我的C#项目中,我有一个带有.ToList()的查询结尾处: public List<Product> ListAllProducts() { List<Product> products = db.Products .Where(...) .Select(p => new Product() { ProductId = p.ProductId, ...
.ToList()的查询代码>结尾处:
public List<Product> ListAllProducts()
{
List<Product> products = db.Products
.Where(...)
.Select(p => new Product()
{
ProductId = p.ProductId,
...
})
.ToList();
return products;
}
到目前为止,它似乎是有效的,我每次都会收到我的数据(以防万一,我还添加了try-catch)
现在我正在进行单元测试,我面临一个问题。我的UnitTest中有一个MockDbContext
,但是由于我使用的是using(…)
,它使用的是非Mock版本,所以我的UnitTests失败了。如何使用MockDbContext而不是默认的上下文来测试这一点
编辑:
我只是偶然发现。因此,在我看来,我确实需要将它一直传递给我的方法,或者只是不使用using(MyDbContext usingDb=new MyDbContext())
,而只是临时增加CommandTimeout并使用try-catch。真的没有别的办法吗?或者,如果不同时使用(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(...)
{
...
}
}
}
在我的UnitTest代码中,我创建了如下实例:
ProductController controller = new ProductController(new MyDbContext());
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;
}
ProductController=newproductcontroller(this.GetTestDb());
//对于GetTestDb,类似于以下内容:
私有IMyDbContext GetTestDb()
{
var memoryProducts=新产品{…};
...
var mockDB=new Mock();
mockDb.Setup(m=>m.Products).Returns(memoryProducts);
...
返回mockDB.Object;
}
在我作为UnitTest的正常项目中,一切都很好,除了在ListAll方法中使用(MyDbContext usingDb=new MyDbContext())这一部分之外。虽然需要考虑代码以方便测试,但只有在测试中执行的代码分支没有帮助。这实际上是将测试环境配置代码放入您的实时代码中 您需要的是为创建
DbContext
多做一点抽象。您应该传入一个构造DbContext
的委托:
public List<Product> ListAllProducts(Func<DbContext> dbFunc)
{
using(MyDbContext usingDb = dbFunc())
{
// ...
}
}
最重要的是,您现在可以控制使用哪个
DbContext
,包括从配置文件中提取的连接字符串,在你的IOC容器中,永远不要担心它在代码中的任何其他地方。根据我的经验,如果不将代码结构化为单元测试友好的代码,就很难进行适当的单元测试,但是你可以尝试使类泛型,这样它就可以在任何上下文中实例化
public class SomeClass<T> where T: MyDbContext, new()
{
public List<Product> ListAllProducts()
{
using(MyDbContext usingDb = new T())
{
((IObjectContextAdapter)usingDb).ObjectContext.CommandTimeout = 120;
List<Product> products = usingDb.Products.ToList();
return products;
}
}
}
// real-code:
var foo = new SomeClass<MyDbContext>();
// test
var bar = new SomeClass<MockContext>();
public类SomeClass,其中T:MyDbContext,new()
{
公共列表ListAllProducts()
{
使用(MyDbContext usingDb=new T())
{
((IObjectContextAdapter)usingDb.ObjectContext.CommandTimeout=120;
列出产品=使用db.products.ToList();
退货产品;
}
}
}
//实数代码:
var foo=new SomeClass();
//试验
var bar=newsomeclass();
在使用我现在拥有的界面时,使用委托看起来很相似。我有一个MyDbContext
-类和IMyDbContext
-接口,在我的ProductController中我使用:私有IMyDbContext数据库;public ProductController(IMyDbContext\u db){this.db=\u db;}
我应该将其改为委托吗?它看起来能够解决我的问题,但我想在代码中的任何地方更改它之前确保它。是的,您应该将它更改为委托。原因是您希望您的DbContext
s是短期的。好的,谢谢,那么明天我将为所有控制器这样做。在使用代表时,只要一切正常,我就会接受你的回答。另一个注意事项是,如果将其更改为Func,则必须在IMyDbContext中继承IDisposable,或者必须在using语句中将Func的返回值强制转换为(IDisposable)。这两种方法都不是最优的,更好的选择是将委托封装在一个构建器类型的类中,该类本身是一次性的,并在处理DbContext时对其进行处理。
public class ProductDAL
{
private Func<DbContext> _dbFunc;
public ProductDAL(Func<DbContext> dbFunc)
{
_dbFunc = dbFunc;
}
public List<Product> ListAllProducts()
{
using(MyDbContext usingDb = _dbFunc())
{
// ...
}
}
}
public class SomeClass<T> where T: MyDbContext, new()
{
public List<Product> ListAllProducts()
{
using(MyDbContext usingDb = new T())
{
((IObjectContextAdapter)usingDb).ObjectContext.CommandTimeout = 120;
List<Product> products = usingDb.Products.ToList();
return products;
}
}
}
// real-code:
var foo = new SomeClass<MyDbContext>();
// test
var bar = new SomeClass<MockContext>();