1

我正在尝试为一个简单的 mvc 控制器编写一个单元测试,该控制器对数据库进行复杂的 LINQ 查询:

public class HomeController
{
    private readonly DamagesDbContext db;

    public HomeController(DamagesDbContext db)
    {
        this.db = db;
    }

    // GET: /Home/
    [Authorize]
    public ActionResult Index()
    {
        var dashData = (from inc_c in db.incident_content
                       join inc in db.incidents
                       on inc_c.incidentid equals inc.incidentid
                       where inc.currentrevisionnumber == inc_c.revisionnumber
                       group inc_c by 1 into g
                       select new{
                           total = g.Count(),
                           open = g.Count(q => q.incidentstatus == "OPEN"),
                           closed = g.Count(q => q.incidentstatus == "CLOSED")
                       }).SingleOrDefault();

        ViewBag.total = dashData.total;
        ViewBag.open = dashData.open;
        ViewBag.closed = dashData.closed;            

        return View();
    }
}

然后在我的测试中,我有:

var mockDb = new Mock<DamagesDbContext>();
mockDb.Setup(/* What goes here? */);

var homeController = new HomeController(mockDb.Object);

var result = homeController.Index();

// Various asserts go here...

但是我在 Setup() 中要做什么来替换那个复杂的 LINQ 查询呢?

我怎么知道正在调用哪些实际方法?或者他们的论点是什么?

==== 已编辑 ====

我认为我的问题的一部分是 LINQ 表达式虽然有点简洁,但并没有明确说明在哪些对象上调用了哪些方法。

我第一次玩 Resharper,我只是注意到它有一个“将 LINQ 转换为方法链”选项。完成后,上面的 LINQ 表达式变为:

var dashData = (this.db.incident_content.Join(this.db.incidents, inc_c => inc_c.incidentid,
    inc => inc.incidentid, (inc_c, inc) => new {inc_c, inc})
    .Where(@t => @t.inc.currentrevisionnumber == @t.inc_c.revisionnumber)
    .GroupBy(@t => 1, @t => @t.inc_c)
    .Select(g => new
    {
        total = g.Count(),
        open = g.Count(q => q.incidentstatus == "OPEN"),
        closed = g.Count(q => q.incidentstatus == "CLOSED")
    })).SingleOrDefault();

这可能会使需要模拟哪些对象和方法更清楚一些。

4

5 回答 5

5

不要将数据库代码放在控制器中。这将是对它进行单元测试的良好开端。将您的数据库代码移动到一个单独的类,其功能是查询数据库,然后您可以模拟对该类的调用。

于 2013-11-08T00:12:06.470 回答
1

请从您的控制器中解耦数据库访问逻辑将数据库
注入控制器

所以现在你必须测试两个组件一个是 db 服务
另一个是 Controller 服务

请注意,当public ActionResult Index()被调用时,它会调用一些 DBService API 来检索一些结果集
您需要包含测试来断言
* 在各种情况下,索引实际上是在数据库上调用 RIGHT api * 使用 API 返回的结果集并使用相同的结果集viewBag.TOTAL, viewBagOPEN etc

这将我们的控制器测试减少到
1> 模拟 DBService(例如使用 Moq)
2> 设置模拟 DBService 以期望调用 DBService.API
3> 当从控制器到模拟对象的调用确实发生时,返回准备好的结果集
4> 断言调用 DBService.API 的 Index 测试 else 失败
5> 断言当调用确实发生并且返回了预先准备好的结果集时,Index 的输出使用的是相同的。

像您想象的那样,分离的 DBService 将有一个公共 API,
例如 public ResultSet GetAllEntitiesThatAreOpenOrClosed(someparameters here) 他们需要自己进行测试。
再次了解 DBService 类的职责是什么并对其进行测试。

于 2013-11-09T10:37:26.223 回答
1

据我所知,很多人给了你很好的建议,但没有回答这个问题。

从查询中我可以看到您需要模拟两个 DbSet(b.incident_content 和 db.incidents)并将它们添加到 FakeDbContext。然后,无论您最终将查询放在哪里,您都可以使用假上下文对其进行测试。

你可以在这里伪造它:http: //msdn.microsoft.com/en-us/data/dn314431.aspx

于 2014-03-13T14:58:36.203 回答
1

It's not a good practice for big applications to use DbContext inside controllers, but anyway somewhere it will appear and this place should be covered with unit tests too. So...

  1. It's better to extract IDamagesDbContext interface and inject it instead of DamagesDbContext class.
  2. You should use IDbSet<T> interface not DbSet<T> class in your context properties that expose entities.
  3. You should write your own implementation of IDbSet<T>, which simulates in-memory database (you can use already written implementation with NuGet package FakeDbSet).
  4. After everything above is done you just fill your in-memory DB sets with appropriate data to achieve desirable test case.
于 2013-11-08T18:29:39.980 回答
0

在这种情况下,我要做的是首先运行您拥有的代码,一旦您的 dashData 变量中有信息,就将其序列化到一个文件中。

下一步,按照 Colin 的建议,将您的数据库代码拆分为一个单独的业务层对象。

当你想模拟业务层时,你会得到模拟来反序列化你的文件并将捕获的输出发送到你的控制器。

因为您确切地知道 db-'result' 将是什么,所以您现在可以测试您的控制器是否正确响应了这组数据。

通过这种方式,您已经隔离了控制器,并且您的单元测试将始终只测试控制器的功能。

如果不同的数据库结果需要以非常不同的方式处理,那么您可以重复此过程以涵盖您预期的所有不同场景。

为了确保您的数据库返回正确的结果,需要在数据库端进行单元测试,这是我仍在努力解决的问题。

最后一句话,到目前为止,我在单元测试(和 tdd)方面的经验是,我最终得到了一些对单元测试来说相当微不足道的类,而不是两个或三个大类。好消息是我在新代码上的错误率已经大幅下降,而且因为我现在可以进行回归测试,所以我很少将新错误引入旧代码中。我仍在努力掌握集成测试,但我希望能有类似的改进。

于 2013-11-08T16:38:27.340 回答