【问题标题】:Can I mock a method for an object that's created in a method I'm testing?我可以模拟在我正在测试的方法中创建的对象的方法吗?
【发布时间】:2013-12-22 20:08:12
【问题描述】:
[TestMethod]
    public void Can_Login_With_Valid_Credentials()
    {
        //Arrange
        Mock<IMembershipRepository> mockRepository = new Mock<IMembershipRepository>();           

        Mock<LoginViewModel> mockModel = new Mock<LoginViewModel>();
        mockModel.Setup(x => x.IsLoggedIn()).Returns(true);            

        AccountController target = new AccountController(mockRepository.Object);

        //Act
        ActionResult result = target.Login(mockModel.Object);

        //Assert
        Assert.IsNotInstanceOfType(result, typeof(ViewResult));

    }

和控制器中的ActionResult

public ActionResult Login(LoginViewModel viewModel)
    {
        string returnUrl = (string)TempData["ReturnUrl"];           

        if (ModelState.IsValid)
        {
            LoginViewModel model = new LoginViewModel(repository, viewModel);

            if (model.IsLoggedIn())
            {
                if (String.IsNullOrEmpty(returnUrl)) return RedirectToAction("Index", "Home");
                else return Redirect(returnUrl);
            }
            else
            {
                ModelState.AddModelError("Email", "");
                ModelState.AddModelError("Password", "");
            }
        }

        return View(viewModel);
    }

我在ActionMethod 中模拟model.IsLoggedIn() 时遇到问题,这可能是因为我在ActionMethod 中创建了视图模型LoginViewModel 的新实例。这就是为什么单元测试中的mockModel.Setup(x =&gt; x.IsLoggedIn()).Returns(true); 没有缓存它,因为有一个具有该方法的类的新实例。

有什么方法可以在 ActionMethod 中模拟 model.IsLoggedIn() 并使其返回 true

【问题讨论】:

  • 确实是这个问题。您创建一个新的 LoginViewModel 对象而不是操作现有对象是否有特定原因?
  • 一句话,没有。您正在尝试模拟一个具体的实现。如果没有注入依赖项,则无法模拟它。您传入的LoginViewModel 实例不是实际调用IsLoggedIn 的实例,因此设置不匹配。
  • 目前只有 TypeMock Isolator 或 Fakes 可以帮助您。通过一些重构,您可以轻松地对此进行测试,在您的控制器中创建一个protected virtual CreateLoginViewModel 将允许您通过普通的模拟工具对其进行操作。或者按照 Jeroen 的建议重用模型。
  • 是的,我也是这么想的!如果不使用现有的 LoginViewModel,我无法模拟它。

标签: c# unit-testing mocking moq


【解决方案1】:

如果没有办法避免在 action 方法中创建 LoginViewModel 的新实例,那么引入一个工厂来执行此操作。不惜一切代价避免直接创建任何具体类 - 这使得单元测试变得不可能。

如果 action 方法使用工厂创建对象,则具体工厂作为控制器的构造函数参数传递。这需要 IoC 容器和自定义控制器工厂,添加到项目中相对简单。

有了这个解决方案,单元测试实际上模拟了工厂,以便模拟工厂返回模拟的 LoginViewModel 对象(实际上:实现 ILoginViewModel 接口的模拟对象)。这是我在所有 MVC 项目中使用的方式,它非常适用于生产代码、单元测试和集成测试。

【讨论】:

  • 感谢您的好主意!这也是我从现在开始要做的事情。
【解决方案2】:

根据上面的 cmets 和研究,最好的方法是使用现有的 LoginViewModel 而不是创建它的新实例。这是与 Moq 一起使用的重构后的 ActionResult 愿景。

public ActionResult Login(LoginViewModel viewModel)
    {
        string returnUrl = (string)TempData["ReturnUrl"];           

        if (ModelState.IsValid)
        {
            if (viewModel.IsLoggedIn(repository))
            {
                if (String.IsNullOrEmpty(returnUrl)) return RedirectToAction("Index", "Home");
                else return Redirect(returnUrl);
            }
            else
            {
                ModelState.AddModelError("Email", "");
                ModelState.AddModelError("Password", "");
            }
        }

        return View(viewModel);
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-07-24
    • 2022-09-28
    • 2021-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-22
    • 2022-09-27
    相关资源
    最近更新 更多