我如何使用 Moq 来测试 returns 从数据库中列出的索引操作?

How can I use Moq to test my Index action that returns list from the database?

我正在学习为 ASP.NET MVC 5 使用单元测试和 Moq。我正在尝试为我的一个控制器的索引操作编写我的第一个单元测试。

这是索引操作的代码。

[Authorize]
public class ExpenseController : Controller
{
    private ApplicationDbContext db = new ApplicationDbContext();

    // GET: /Expense/
    public ActionResult Index()
    {
        return View(db.Expenses.ToList().Where(m => m.ApplicationUserId == User.Identity.GetUserId()));
    }
}

我只想检查返回的视图是否为空

像这样

    [TestMethod]
    public void ExpenseIndex()
    {
        // Arrange
        ExpenseController controller = new ExpenseController();

        // Act
        ViewResult result = controller.Index() as ViewResult;

        // Assert
        Assert.IsNotNull(result);
    }

当然,由于连接到数据库和使用ApplicationUserId,这不起作用,所以你们能帮我对这个动作进行最小起订量和单元测试,或者给我推荐一个我可以的教程熟悉 ASP.NET MVC 中的模拟。

一种方法是抽象将依赖封装在虚方法中,例如:创建一个return用户开销的虚方法,现在你的控制器应该是这样的:

public class ExpenseController : Controller
    {
        private ApplicationDbContext db = new ApplicationDbContext();

        // GET: /Expense/
        public ActionResult Index()
        {
            return View(GetUserExpenses());
        }

        protected virtual List<Expense> GetUserExpenses()
        {
            return db.Expenses.ToList().Where(m => m.ApplicationUserId == User.Identity.GetUserId());
        }
    }

然后,创建一个从您的控制器派生的存根 class,并覆盖 GetUserExpenses() 方法。它应该看起来像:

public class ExpenseControllerStub : ExpenseController
    {
        protected override List<Expense> GetUserExpenses()
        {
            return new List<Expense>();
        }
    }

现在在您的单元测试中,从 ExpenseControllerStub 而不是 ExpenseController 创建实例,它应该可以工作:

[TestMethod]
    public void ExpenseIndex()
    {
        // Arrange
        ExpenseControllerStub controller = new ExpenseControllerStub();

        // Act
        ViewResult result = controller.Index() as ViewResult;

        // Assert
        Assert.IsNotNull(result);
    }

这是手动操作的方法。如果您需要为此使用模拟框架,则需要使 GetUserExpenses() public 不受保护,然后设置 return 一个空的费用列表,例如:

var mock = new Moq.Mock<ExpenseController>();
mock.Setup(m => m.GetUserExpenses().Returns(new List<Expense>());

但我不喜欢用这种方法public!可能 Moq 有办法 configure/setup 保护方法,但我不知道。

编辑:一个更好的解决方案是完全抽象 Expenses 存储库,在这种情况下模拟它会很简单。

另一种解决方案是将 DbContext 注入控制器构造函数,并使用模拟框架来模拟它和 Expenses DbSet。您可以找到执行此操作的示例 here

编辑 #2:您也可以使用 TestStack.FluentMVCTesting or MvcContrib.TestHelper 来简化 MVC 测试。