2012-04-11 154 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(...)