在没有命中数据库的情况下测试ASP.NET MVC控制器的干净方法?

时间:2010-02-17 13:21:55

标签: asp.net-mvc unit-testing mocking rhino-mocks

我正在尝试一种清理控制器的好方法,使其更易于测试,而不必依赖于持续的数据库连接。我认为通过使用IObjectContext抽象出我的对象上下文,我有一个不错的开始。这适用于上下文,但我的下一个问题是我有一个generic repository我在整个项目中使用了很多动作方法(参见下面的代码)。

除了默认构造函数之外,我的控制器还包含一个重载,它接受IObjectContext(简单依赖注入)。在我的单元测试中,我可以轻松地模拟IObjectContext。我的问题是在各种动作方法中处理我的通用存储库。我可以向控制器添加一些额外的构造函数重载,但我担心这会很快变得混乱。但是,如果没有这样做,我根本无法想到一种提高可测试性的简洁方法,因此我不必依赖数据库连接。

我有一个简单的解决方案吗?

/// <summary>
/// Initializes a new instance of the HomeController class
/// </summary>
public HomeController(IObjectContext context)
{
    _context = context;
}

/// <summary>
/// GET: /home/index
/// </summary>
/// <returns>Renders the home page</returns>
public ActionResult Index()
{
    List contacts;
    HomeViewModel model;

    using (IRepository<Contact> repository = new DataRepository<Contact>(_context))
    {
        contacts = new List(repository.GetAll());
    }

    model = new HomeViewModel(contacts);

    return View(model);
}

如果我必须采用添加额外构造函数重载的路径来满足我的需求,我正考虑在每个存储库的控制器中添加一些私有属性(这将会对存储库进行实例化,直到需要它们为止)行动方法利用。例如:

private IRepository<Contact> _contactRepository;

private IRepository<Contact> ContactRepository
{
    get
    {
        return _contactRepository ?? (_contactRepository = new DataRepository<Contact>());
    }
}

出于单元测试的目的,我可以使用构造函数重载来预初始化存储库。

您对此有何看法?我错过了一些应该很明显的清洁工具吗?

3 个答案:

答案 0 :(得分:5)

首先,摆脱当前的 Bastard Injection 构造函数重载。使用DI,您应该只需要一个构造函数,这就是获取所有依赖项的构造函数。 (要启用ASP.NET MVC运行时以创建控制器,请实现自定义IControllerFactory。)

下一步是通过构造函数注入所有依赖项。当你认为它因为构造函数参数太多而变得混乱时,这是一个很好的信号,表明你违反了Single Responsibility Principle。发生这种情况时,您会提取Aggregate Service

冲洗并重复:)

答案 1 :(得分:1)

好吧,我做的是你的最后一个例子,它总是将mocks注入我的控制器。它确实有点气味(设计可测试性),但编码也不错,非常适合测试。

答案 2 :(得分:1)

您使用通用存储库更像是依赖项隐藏设备,而不是依赖注入。您应该能够看到特定Controller使用的所有依赖项:通用存储库会在控制器的内部深处隐藏这一事实,这使得维护(和单元测试)代码变得更加困难。我的建议:使用具体的存储库。

您还可以查看domain-driven design stuff