【问题标题】:Basic MVC3 UnitTest fails to UpdateModel()基本 MVC3 UnitTest 无法 UpdateModel()
【发布时间】:2012-04-23 18:44:15
【问题描述】:

我已经有一段时间没有做任何 MVC 工作了,所以我希望我错过了一些东西。我正在尝试编写一个测试和控制器操作来简单地编辑一个名为“业务”的 DTO。

控制器动作:

[HttpPost]
public ActionResult Edit(string id, Business business)
{
    try
    {
        var model = _businessRepository.Get(id);

        if (model != null)
        {
            UpdateModel(model);

            if (ModelState.IsValid)
            {
                _businessRepository.Save(model);
            }
            else
            {
                return View(business);
            }
        }

        return RedirectToAction("Index");
    }
    catch
    {
        return View();
    }
}

测试:

[TestMethod]
public void Edit_Post_Action_Updates_Model_And_Redirects()
{
    // Arrange
    var mockBusinessRepository = new Mock<IBusinessRepository>();
    var model = new Business { Id = "1", Name = "Test" };
    var expected = new Business { Id = "1", Name = "Not Test" };

    // Set up result for business repository
    mockBusinessRepository.Setup(m => m.Get(model.Id)).Returns(model);
    mockBusinessRepository.Setup(m => m.Save(expected)).Returns(expected);
    var businessController = new BusinessController(mockBusinessRepository.Object);

    // Act
    var result = businessController.Edit(model.Id, expected) as RedirectToRouteResult;

    // Assert
    Assert.IsNotNull(result);
    Assert.AreEqual(result.RouteValues["action"], "Index");
    mockBusinessRepository.VerifyAll();
}

它给出异常的行是控制器中的 UpdateModel()。异常详情如下:

“值不能为空。参数名称:controllerContext”

【问题讨论】:

  • 调用 save 方法时模型中有什么东西吗?模型的代码是什么?
  • 没有UpdateModel() 的代码很难说,但我的猜测是它依赖于一个不是由您的单元测试构建的数据库上下文。
  • @Brian 我从来没有使用过 Save 方法,因为它在 UpdateModel 上死掉了。但模型只是 2 个字符串:Id 和 Name。
  • @GalacticCowboy UpdateModel() 函数内置在 MVC 中。我没有写。因为我没有使用 SQL,所以没有要构造的数据库上下文。我正在模拟存储库,这样我什至不必依赖存储库。这些在其他地方进行了测试。

标签: c# asp.net-mvc unit-testing


【解决方案1】:

我在 Gist 上有一些代码,通常用于设置 ControllerContext。该代码是最初取自 Hanselman 博客的修改版本。

https://gist.github.com/1578697 (MvcMockHelpers.cs)

【讨论】:

  • 这也会导致“对象引用未设置为对象的实例”。更新模型错误。它似乎仍然不喜欢它。
【解决方案2】:

设置控制器上下文

以下是我从事的一个项目的代码 sn-p,所以可能对你来说太多了

public class TestBase
    {
        internal Mock<HttpContextBase> Context;
        internal Mock<HttpRequestBase> Request;
        internal Mock<HttpResponseBase> Response;
        internal Mock<HttpSessionStateBase> Session;
        internal Mock<HttpServerUtilityBase> Server;
        internal GenericPrincipal User;

            public void SetContext(Controller controller)
            {
              Context = new Mock<HttpContextBase>();
              Request = new Mock<HttpRequestBase>();
              Response = new Mock<HttpResponseBase>();
              Session = new Mock<HttpSessionStateBase>();
              Server = new Mock<HttpServerUtilityBase>();
       User = new GenericPrincipal(new GenericIdentity("test"), new string[0]);

              Context.Setup(ctx => ctx.Request).Returns(Request.Object);
              Context.Setup(ctx => ctx.Response).Returns(Response.Object);
              Context.Setup(ctx => ctx.Session).Returns(Session.Object);
              Context.Setup(ctx => ctx.Server).Returns(Server.Object);
              Context.Setup(ctx => ctx.User).Returns(User);

              Request.Setup(r => r.Cookies).Returns(new HttpCookieCollection());
              Request.Setup(r => r.Form).Returns(new NameValueCollection());
      Request.Setup(q => q.QueryString).Returns(new NameValueCollection());
              Response.Setup(r => r.Cookies).Returns(new HttpCookieCollection());

              var rctx = new RequestContext(Context.Object, new RouteData());
controller.ControllerContext = new ControllerContext(rctx, controller);
            }
}

那么在你的测试中你可以安排:

//Arrange
SetContext(_controller);
Context.Setup(ctx => ctx.Request).Returns(Request.Object);

如果您想用 ModelState 错误测试您的方法,请添加:

_controller.ModelState.AddModelError("Name", "ErrorMessage");

【讨论】:

  • 虽然这看起来确实像我一直在阅读的内容,但由于某种原因,它仍然会导致代码在 UpdateModel 上死掉。 “对象引用未设置为对象的实例。”
  • 我看到我已经设置了响应,在你的情况下它必须是请求。我认为这也是您的 Null 引用异常
【解决方案3】:

您需要为您的BusinessController 模拟ControllerContext

this questionthis one

【讨论】:

    【解决方案4】:

    我已经设法通过使用 Automapper 而不是 UpdateModel 来实现我想要的工作。

    我在自动映射器初始化中添加了(IPersistable 是我所有 DTO 的接口):

    Mapper.CreateMap<IPersistable, IPersistable>().ForMember(dto => dto.Id, opt => opt.Ignore());
    

    然后我将控制器操作更改为:

    [HttpPost]
    public ActionResult Edit(string id, Business business)
    {
        try
        {
            var model = _businessRepository.Get(id);
    
            if (model != null)
            {
                Mapper.Map(business, model);
    
                if (ModelState.IsValid)
                {
                    _businessRepository.Save(model);
                }
                else
                {
                    return View(business);
                }
            }
    
            return RedirectToAction("Index");
        }
        catch
        {
            return View();
        }
    }
    

    并将我的测试更改为:

    [TestMethod]
    public void Edit_Post_Action_Updates_Model_And_Redirects()
    {
        // Arrange
        var mockBusinessRepository = new Mock<IBusinessRepository>();
        var fromDB = new Business { Id = "1", Name = "Test" };
        var expected = new Business { Id = "1", Name = "Not Test" };
    
        // Set up result for business repository
        mockBusinessRepository.Setup(m => m.Get(fromDB.Id)).Returns(fromDB);
        mockBusinessRepository.Setup(m => m.Save(It.IsAny<Business>())).Returns(expected);
        var businessController = new BusinessController(mockBusinessRepository.Object) {ControllerContext = new ControllerContext()};
    
        //Act
        var result = businessController.Edit(fromDB.Id, expected) as RedirectToRouteResult;
    
        // Assert
        Assert.IsNotNull(result);
        Assert.AreEqual(result.RouteValues["action"], "Index");
        mockBusinessRepository.VerifyAll();
    }
    

    【讨论】:

      【解决方案5】:

      我遇到了同样的问题,并使用堆栈跟踪将其锁定到 ValueProvider。基于 Andrew 上面关于模拟控制器使用的一些底层对象的答案,我设法通过像这样模拟 ValueProvider 来解决空值异常:

      var controller = new MyController();
      
      // ... Other code to mock objects used by controller ...
      
      var mockValueProvider = new Mock<IValueProvider>();
      controller.ValueProvider = mockValueProvider.Object;
      
      // ... rest of unit test code which relies on UpdateModel(...)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-12-19
        • 2011-12-16
        • 1970-01-01
        • 1970-01-01
        • 2012-05-17
        • 2021-01-30
        • 2015-08-29
        • 1970-01-01
        相关资源
        最近更新 更多