繁体   English   中英

在UnitTest Mock-DbContext上使用

[英]Using on UnitTest Mock-DbContext

在我的C#项目中,我使用.ToList();查询.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(...) ,它使用了非Mock版本,所以我的UnitTests失败了。 我如何使用MockDbContext而不是默认值进行测试?


编辑:

我刚刚碰到有关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 您应该传递一个构造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容器中,而DbContext担心代码中的其他任何地方。

以我的经验,如果不将代码结构化为对单元测试友好的话,就很难进行适当的单元测试,但是您可以尝试使该类通用,以便可以使用任一上下文将其实例化。

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>();

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM