在我的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/

10-10 00:11