C# 在UnitTest模拟DbContext上使用

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, ...

在我的C#项目中,我有一个带有
.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>();