基本 MVC3 单元测试无法更新模型()

本文关键字:模型 更新 MVC3 单元测试 基本 | 更新日期: 2023-09-27 18:30:49

我已经有一段时间没有做任何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()。 异常详细信息包括:

"值不能为空。 参数名称:控制器上下文"

基本 MVC3 单元测试无法更新模型()

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

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

设置控制器上下文

以下是我正在处理的一个项目的代码片段,所以也许这对你来说意义重大

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);

如果要使用模型状态错误测试方法,请添加:

_controller.ModelState.AddModelError("Name", "ErrorMessage");
你需要为

你的BusinessController模拟ControllerContext

看到这个问题或这个问题。

我已经通过使用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();
}

我遇到了同样的问题,并使用堆栈跟踪将其固定到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(...)