2012-04-11 157 views
0

自从我做任何MVC工作已经有一段时间了,所以我希望错过一些东西。我试图编写测试和控制器操作来简单地编辑名为“Business”的DTO。基本MVC3单元测试失败UpdateModel()

控制器动作:

[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”

+1

有什么在模型中,当你调用save方法?模型的代码是什么? – Brian 2012-04-11 12:59:13

+0

没有'UpdateModel()'的代码很难说,但我的猜测*是它依赖于不是由你的单元测试构造的数据库上下文。 – GalacticCowboy 2012-04-11 13:27:48

+0

@Brian我从来没有去过Save方法,因为它在UpdateModel上死了。但模型只是2个字符串:Id和Name。 – mandreko 2012-04-11 14:31:06

回答

0

我已经成功地得到我想要通过使用Automapper代替的UpdateModel工作。

我在automapper初始化加(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(); 
} 
1

安装的控制器上下文

以下是我工作的一个项目的代码片段,所以也许这是给你累

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"); 
+0

尽管这看起来像我一直在阅读的内容,但由于某些原因,它仍然导致代码在UpdateModel上死亡。 “你调用的对象是空的。” – mandreko 2012-04-11 14:41:05

+0

我看到我已经设置了响应,它必须是您的情况下的请求。我认为这也是你的空引用异常 – 2012-04-11 18:10:15

0

我有同样的问题,并使用堆栈跟踪将其固定到ValueProvider。对安德鲁的回答上面嘲讽一些由控制器使用的底层对象的建设,我设法还嘲讽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(...)