2009-09-14 84 views
0

我很新的测试和嘲笑,我想写一个测试,以确保我的验证逻辑正确设置ModelState错误。模拟MVC ControllerContext请求的问题

我所看到的是,controller.ControllerContext.HttpContext.Request设为我第一次检查,但在此之后,请求为空每次。

这导致在MVC源* ValueProviderDictionary *类的PopulateDictionary方法一个空引用异常,因为请求对象被访问,该方法中多次而不确保请求不为空。

我将几种技术和帮手都粘在了一起,这些技巧和帮手是在研究如何克服迄今为止遇到的一些问题的时候发现的,因此在这一点上我有点不确定我可能在哪里介绍了该问题。

我在这里使用模拟对象不正确吗?

失败的测试

//Test 
public void Test_FooController_OnActionExecuting_ShouldMapStateToAFooModel() 
{ 
    //Arrange 
    DataAccessFactoryMocks.MockAllDaos(); 

    var controller = new FooController(); 

    var testFormCollection = new NameValueCollection(); 
    testFormCollection.Add("foo.CustomerID", "3"); 
    testFormCollection.Add("_fooForm", SerializationUtils.Serialize(new FooModel())); 

    var mockHttpContext = new MockHttpContext(controller, "POST", testFormCollection, null); 

    //Accessor used to run the protected OnActionExecuting method in my controller 
    var accessor = new FooControllerAccessor(controller); 

    //Request is set, assertion passes 
    Assert.IsNotNull(controller.ControllerContext.HttpContext.Request.Form); 

    //Request is null when accessing the property a second time, assertion fails 
    Assert.IsNotNull(controller.ControllerContext.HttpContext.Request.QueryString); 

    //Act 
    accessor.OnActionExecuting(new ActionExecutingContext(controller.ControllerContext, MockRepository.GenerateStub<ActionDescriptor>(), new Dictionary<string, object>())); 

    //Assert 
    Assert.That(controller.ModelState.IsValid == false); 
} 

测试助手

//Test helper to create httpcontext and set controller context accordingly 
public class MockHttpContext 
{ 
    public HttpContextBase HttpContext { get; private set; } 
    public HttpRequestBase Request { get; private set; } 
    public HttpResponseBase Response { get; private set; } 
    public RouteData RouteData { get; private set; } 

    public MockHttpContext(Controller onController) 
    { 
     //Setup the common context components and their relationships 
     HttpContext = MockRepository.GenerateMock<HttpContextBase>(); 
     Request = MockRepository.GenerateMock<HttpRequestBase>(); 
     Response = MockRepository.GenerateMock<HttpResponseBase>(); 

     //Setup the context, request, response relationship 
     HttpContext.Stub(c => c.Request).Return(Request); 
     HttpContext.Stub(c => c.Response).Return(Response); 

     Request.Stub(r => r.Cookies).Return(new HttpCookieCollection()); 
     Response.Stub(r => r.Cookies).Return(new HttpCookieCollection()); 

     Request.Stub(r => r.QueryString).Return(new NameValueCollection()); 
     Request.Stub(r => r.Form).Return(new NameValueCollection()); 

     //Apply the context to the suppplied controller 
     var rc = new RequestContext(HttpContext, new RouteData()); 
     onController.ControllerContext = new ControllerContext(rc, onController); 
    } 

    public MockHttpContext(Controller onController, string httpRequestType, NameValueCollection form, NameValueCollection querystring) 
    { 
    //Setup the common context components and their relationships 
    HttpContext = MockRepository.GenerateMock<HttpContextBase>(); 
    Request = MockRepository.GenerateMock<HttpRequestBase>(); 
    Response = MockRepository.GenerateMock<HttpResponseBase>(); 

    //Setup request type based on parameter value 
    Request.Stub(r => r.RequestType).Return(httpRequestType); 

    //Setup the context, request, response relationship 
    HttpContext.Stub(c => c.Request).Return(Request); 
    HttpContext.Stub(c => c.Response).Return(Response); 

    Request.Stub(r => r.Cookies).Return(new HttpCookieCollection()); 
    Response.Stub(r => r.Cookies).Return(new HttpCookieCollection()); 

    Request.Stub(r => r.QueryString).Return(querystring); 
    Request.Stub(r => r.Form).Return(form); 

    //Apply the context to the suppplied controller 
    var rc = new RequestContext(HttpContext, new RouteData()); 
    onController.ControllerContext = new ControllerContext(rc, onController); 
    } 
} 

工作测试使用MvcContrib.TestHelper

public void Test_FooController_OnActionExecuting_ShouldMapStateToAFooModel() 
    { 
     //Arrange 
     DataAccessFactoryMocks.MockAllDaos(); 

     TestControllerBuilder builder = new TestControllerBuilder(); 

     builder.Form.Add("fooModel.CustomerID", "3"); 

     builder.HttpContext.Request.Stub(r => r.RequestType).Return("POST"); 

     FooController controller = builder.CreateController<FooController>(); 

     var accessor = new FooControllerAccessor(controller); 

     //Act 
     accessor.OnActionExecuting(new ActionExecutingContext(controller.ControllerContext, MockRepository.GenerateStub<ActionDescriptor>(), new Dictionary<string, object>())); 

     //Assert 
     Assert.IsFalse(controller.ModelState.IsValid); 
    } 

回答

5

我建议你使用优秀的MVCContrib TestHelper来测试你的ASP.NET MVC控制器和Rhino Mocks。您会看到单元测试的大幅度简化并提高了可读性。

+0

谢谢,我会考虑他们,因为我最终会喜欢为每个测试消除所有这些设置。尽管如此,我仍然不明白为什么房产被清理,我想写一些冗长的测试来帮助我理解发生的事情。 TestHelper似乎隐藏了很多我认为有益于理解的内部工作。 – jjr2527 2009-09-14 17:28:32

+0

因为当你定义期望时,你需要指定.Repeat.Any() – 2009-09-14 17:59:29

+0

我使用TestHelper工作。 .Repeat.Any()仍然没有修复原始测试,所以我必须仍然做错了什么。我很满意什么出来。上面更新的代码。 – jjr2527 2009-09-14 18:50:01

0

我从你的问题中了解到,ControllerContext的模拟也可以被存根对象所取代,因为目标不是测试ControllerContext的行为。另外,我也不知道为什么你需要一个FooControllerAccessor而你唯一关心的是断言的ModelState,所以我把它出在这里:

public void Test_FooController_OnActionExecuting_ShouldMapStateToAFooModel() 
{ 
    // Arrange 
    var action = new FooController() 
     .Action("index") 
     .RequestData(new Dictionary<string, object>() 
     { 
      {"foo.CustomerID", 3}, 
      {"_fooForm", new FooModel()} 
     }); 

    //Act 
    var modelState = action.ValidateRequest(); 

    //Assert 
    Assert.That(modelState.IsValid == false); 
} 

要使用此代码,您应该安装Xania.AspNet.Simulator(在写V1.4.0,beta5的时间)工程Mvc4和Mvc5

PM>安装包Xania.AspNet。模拟器 - 预

更多的例子,请查看以下内容: