使用Moq模拟HttpContextBase

23

我有一个单元测试工具,其中我试图测试用于Web应用程序成员资格功能的ASP.NET MVC控制器上的ControllerAction。我正在尝试为测试模拟HttpContext。受测试的ControllerAction实际上在HttpContext上设置属性,例如Session值、Response.Cookies值等。这不是所有代码,但这是我试图运行的测试的大致示例:

[Test]
public void ValidRegistrationDataSuccessfullyCreatesAndRegistersUser()
{
    var context = new Mock<HttpContextBase>() {DefaultValue = DefaultValue.Mock};
    context.SetupAllProperties();
    var provider = new Mock<MembershipProvider>(new object[] {context.Object});
    var controller = new AccountController(context.Object, provider.Object);
    // This just sets up a local FormCollection object with valid user data 
    // in it to use to attempt the registration
    InitializeValidFormData();
    ActionResult result = controller.Register(_registrationData);
    Assert.IsInstanceOfType(typeof(ViewResult), result);
    // Here is where I'd like to attempt to do Assertions against properties 
    // of the HttpContext, like ensuring that a Session object called "User" 
    // exists, and new auth cookie exists on the Response.Cookies collection. 
    // So far I've been unable to successfully check the values of those properties.
    // I've been unsuccessful in getting those properties setup correctly on my 
    // mock object so that my ControllerAction can actually *set* their values, 
    // and that I can make assertions on them afterwards. The above code actually
    // generates a StackOverflowException (which I've reported) on the
    // context.SetupAllProperties() call. What am I doing wrong, or what do I need 
    // to do to be able to set and assert on those context properties?
}

我不确定自己做错了什么,但如果有人能指点一下我该怎么设置这个模拟的HttpContextBase对象,使得我的控制器可以在其属性上设置值,并且我可以对这些属性进行断言,以确保我的ControllerAction正在按照我的需要执行。

我是否走错了方向?我知道MVC控制器有一个ControllerContext属性,可以用于设置Session等值,但我无法想象如何在没有注入的情况下模拟它。有没有其他方法可以做到这一点?(我还需要将该上下文传递给我的MembershipProvider)这种方法会更好吗?

谢谢。

2个回答

33

我正在使用 Steve Sanderson 在他的 Pro Asp.NET MVC 书籍 中提供的某些代码版本... 我目前面临一个道德困境,是否可以在这里发布该代码。如果只发布高度简化的版本,怎么样? ;)

为了方便重复使用,创建一个类似下面的类,并将其传递给控制器。这将设置您的模拟对象并将它们设置到控制器的 ControllerContext 中。

public class ContextMocks
{
    public Moq.Mock<HttpContextBase> HttpContext { get; set; }
    public Moq.Mock<HttpRequestBase> Request { get; set; }
    public RouteData RouteData { get; set; }

    public ContextMocks(Controller controller)
    {
        //define context objects
        HttpContext = new Moq.Mock<HttpContextBase>();
        HttpContext.Setup(x => x.Request).Returns(Request.Object);
        //you would setup Response, Session, etc similarly with either mocks or fakes

        //apply context to controller
        RequestContext rc = new RequestContext(HttpContext.Object, new RouteData());
        controller.ControllerContext = new ControllerContext(rc, controller);
    }
}

然后在您的测试方法中,您只需创建一个ContextMocks实例,并传入要测试的控制器对象:

[Test]
Public void test()
{
     var mocks = new ContextMocks(controller);
     var req = controller.Request; 
     //do some asserts on Request object
}

看起来与Craig的示例非常相似,但这是使用Moq v3。我必须要感谢Steve Sanderson - 我将其用作测试各种其他传统上难以测试的内容的基础:cookie、session、请求方法、查询字符串等等!


我有他的书,但是只是几天前才拿到,所以我想我还没有看到那一部分。如果你能告诉我在那本书的哪个章节里找到了样例代码,那就太好了。非常感谢。 - Bob Yexley
看看第9章的结尾。虽然我手头没有书,但在亚马逊书籍的目录中瞥了一眼,似乎就是那个。 - Kurt Schindler
找到了。那正是我所需要的。现在一切都运转良好了。非常感谢。 - Bob Yexley
这是一个不错的模式,比我过去使用构造函数注入上下文包装器要好得多。 - jcvandan
1
需要在//定义上下文对象之上添加以下内容:Request = new Moq.Mock<HttpRequestBase>(); - ZagNut

25

这是我如何做到的

    public static HttpContextBase FakeHttpContext()
    {
        var context = new Mock<HttpContextBase>();
        var request = new Mock<HttpRequestBase>();
        var response = new Mock<HttpResponseBase>();
        var session = new Mock<HttpSessionStateBase>();
        var server = new Mock<HttpServerUtilityBase>();
        var user = new Mock<IPrincipal>();
        var identity = new Mock<IIdentity>();

        request.Expect(req => req.ApplicationPath).Returns("~/");
        request.Expect(req => req.AppRelativeCurrentExecutionFilePath).Returns("~/");
        request.Expect(req => req.PathInfo).Returns(string.Empty);
        response.Expect(res => res.ApplyAppPathModifier(It.IsAny<string>()))
            .Returns((string virtualPath) => virtualPath);
        user.Expect(usr => usr.Identity).Returns(identity.Object);
        identity.ExpectGet(ident => ident.IsAuthenticated).Returns(true);

        context.Expect(ctx => ctx.Request).Returns(request.Object);
        context.Expect(ctx => ctx.Response).Returns(response.Object);
        context.Expect(ctx => ctx.Session).Returns(session.Object);
        context.Expect(ctx => ctx.Server).Returns(server.Object);
        context.Expect(ctx => ctx.User).Returns(user.Object);

        return context.Object;
    }

这是Scott Hanselman发布的MvcMockHelpers库的增强版。这是Moq 2.0代码;在3中语法略有不同。


是的,谢谢,但我正在使用Moq 3.1.416.3,所以看到该版本的语法确实会很有帮助。主要是因为如果我理解正确的话,在3.x版本中“设置”属性与之前有相当大的不同。不过还是感谢您提供的示例。 - Bob Yexley
request.Expect(req => req.ApplicationPath).Returns("~/"); 应该改为 request.Expect(req => req.ApplicationPath).Returns("/"); - Jim Geurts

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接