我对测试和模拟还很陌生,我正在尝试编写一个测试来确保我的验证逻辑正确设置 ModelState 错误。

我看到的是 控制器.ControllerContext.HttpContext.Request 我第一次检查时已设置,但此后每次 要求 一片空白。

这会导致空引用异常 填充字典 MVC 源中的 *ValueProviderDictionary * 类的方法,因为在该方法中多次访问请求对象,而不确保请求不为 null。

我正在将我在研究如何克服迄今为止遇到的一些问题时发现的几种技术和帮助程序结合在一起,所以此时我有点不确定我可能在哪里引入了这个问题。

我在这里错误地使用了模拟对象吗?

测试失败

//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);
    }
有帮助吗?

解决方案

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

其他提示

我从你的问题中了解到的是,对 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

下午 > 安装包 Xania.AspNet.Simulator -Pre

有关更多示例,请查看以下内容:

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top