无法使用Moq在ASP.NET MVC控制器中模拟方法调用

5

我在我的ASP.NET MVC应用程序中的“UserController”中有以下代码:

public class UserController : Controller
{
    public ActionResult Index()
    {
        return RedirectToAction("List");
    }

    public ActionResult List()
    {
        IUserRepository repo = new UserRepository();
        IQueryable<Business.Entities.User> users = repo.GetAll();
        return View("List", users);
    }
}

使用 Moq,我想模拟数据库调用 "repo.GetAll()"。下面是我的测试代码:

[Test]
public void List()
{
    Mock<IUserRepository> mockRepo = new Mock<IUserRepository>();
    mockRepo.Setup(ur => ur.GetAll()).Returns(MockedGetAll());
    var v = mockRepo.Object.GetAll();

    var controller = new UserController();
    var result = controller.List() as ViewResult;
    var model = result.ViewData.Model as IQueryable<User>;

    Assert.AreEqual("List", result.ViewName);
    Assert.IsNotNull(model);
    Assert.Greater(model.Count(), 0);
}

我有一个函数,可以返回一些静态数据来满足测试需求:
private IQueryable<User> MockedGetAll()
{
    List<User> users = new List<User>();
    users.Add(new User(1, "mark.paterson", "mark.paterson@yahoo.com", "Mark", "Paterson", false, true));
    users.Add(new User(2, "nikki.paterson", "nikki.paterson@yahoo.com", "Nikki", "Paterson", false, true));
    return users.AsQueryable();
}

测试在“Assert.Greater”处中断。 我获得了0个记录而不是2个。 当我调试代码时,实际上返回的是数据库调用的结果,这应该是0个记录,而不是模拟数据。

1个回答

15
以下代码会破坏一切并引入一个无法单元测试隔离的强耦合,它位于您的控制器和数据访问层之间:
IUserRepository repo = new UserRepository();

无论是在ASP.NET MVC还是其他任何应用程序中,绝对不要编写这样的代码。如果您编写了这样的代码,它将始终在单元测试中破坏,并且您将无法测试它。

这是不可能进行模拟/单元测试的。

您应该使用构造函数注入来减弱层之间的耦合:

public class UserController : Controller
{
    private readonly IUserRepository _repo;
    public UserController(IUserRepository repo)
    {
        _repo = repo;
    }

    public ActionResult Index()
    {
        return RedirectToAction("List");
    }

    public ActionResult List()
    {
        IQueryable<Business.Entities.User> users = _repo.GetAll();
        return View("List", users);
    }
}

现在你可以通过模拟来进行隔离单元测试:

[Test]
public void List()
{
    Mock<IUserRepository> mockRepo = new Mock<IUserRepository>();
    mockRepo.Setup(ur => ur.GetAll()).Returns(MockedGetAll());
    var v = mockRepo.Object.GetAll();

    var controller = new UserController(mockRepo.Object);
    var result = controller.List() as ViewResult;
    var model = result.ViewData.Model as IQueryable<User>;

    Assert.AreEqual("List", result.ViewName);
    Assert.IsNotNull(model);
    Assert.Greater(model.Count(), 0);
}

显然,现在你的控制器依赖于这个仓库,你可以使用DI框架来配置你的依赖关系。

不想使用DI框架的人通常会编写像这样的代码并提供2个构造函数(一个用于单元测试,一个用于实际应用):

private readonly IUserRepository _repo;
public UserController(IUserRepository repo)
{
    _repo = repo;
}

public UserController(): this(new UserRepository())
{

}

我提供这个例子是为了说明另一件你绝对不应该做的事情,并强调这是“穷人版依赖注入”(poor man's DI)

Haacked也在他的博客文章中讨论了这些问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接