非单元测试MVC站点

本文关键字:站点 MVC 单元测试 | 更新日期: 2023-09-27 18:08:58

我遇到了一个问题,试图单元测试一个MVC网站,我有:我需要很多的ASP。. NET环境要运行(生成httpcontext, sessions, cookies, membership等)来全面测试一切。

即使是测试一些不那么前端的东西也需要会员资格才能正常工作,而且手工欺骗这些东西是很挑剔的。

是否有办法在NUnit测试中启动应用程序池?这似乎是最简单的方法。

非单元测试MVC站点

如果写得正确,您不需要真正的上下文、真正的会话、cookie等。默认情况下,MVC框架提供了一个可以模拟/存根的HttpContext。我建议使用Moq或Rhino Mocks这样的模拟框架,并创建一个MockHttpContext类,该类创建一个模拟上下文,其中包含您需要针对设置进行测试的所有属性。下面是一个使用Moq

的模拟HttpContext
/// <summary>
/// Mocks an entire HttpContext for use in unit tests
/// </summary>
public class MockHttpContextBase
{
    /// <summary>
    /// Initializes a new instance of the <see cref="MockHttpContextBase"/> class.
    /// </summary>
    public MockHttpContextBase() : this(new Mock<Controller>().Object, "~/")
    {
    }
    /// <summary>
    /// Initializes a new instance of the <see cref="MockHttpContextBase"/> class.
    /// </summary>
    /// <param name="controller">The controller.</param>
    public MockHttpContextBase(Controller controller) : this(controller, "~/")
    {
    }
    /// <summary>
    /// Initializes a new instance of the <see cref="MockHttpContextBase"/> class.
    /// </summary>
    /// <param name="url">The URL.</param>
    public MockHttpContextBase(string url) : this(new Mock<Controller>().Object, url)
    {              
    }
    /// <summary>
    /// Initializes a new instance of the <see cref="MockHttpContextBase"/> class.
    /// </summary>
    /// <param name="controller">The controller.</param>
    /// <param name="url">The URL.</param>
    public MockHttpContextBase(ControllerBase controller, string url)
    {
        HttpContext = new Mock<HttpContextBase>();
        Request = new Mock<HttpRequestBase>();
        Response = new Mock<HttpResponseBase>();
        Output = new StringBuilder();
        HttpContext.Setup(x => x.Request).Returns(Request.Object);
        HttpContext.Setup(x => x.Response).Returns(Response.Object);
        HttpContext.Setup(x => x.Session).Returns(new FakeSessionState());
        Request.Setup(x => x.Cookies).Returns(new HttpCookieCollection());
        Request.Setup(x => x.QueryString).Returns(new NameValueCollection());
        Request.Setup(x => x.Form).Returns(new NameValueCollection());
        Request.Setup(x => x.ApplicationPath).Returns("~/");
        Request.Setup(x => x.AppRelativeCurrentExecutionFilePath).Returns(url);
        Request.Setup(x => x.PathInfo).Returns(string.Empty);
        Response.Setup(x => x.Cookies).Returns(new HttpCookieCollection());
        Response.Setup(x => x.ApplyAppPathModifier(It.IsAny<string>())).Returns((string path) => path);
        Response.Setup(x => x.Write(It.IsAny<string>())).Callback<string>(s => Output.Append(s));
        var requestContext = new RequestContext(HttpContext.Object, new RouteData());
        controller.ControllerContext = new ControllerContext(requestContext, controller);
    }
    /// <summary>
    /// Gets the HTTP context.
    /// </summary>
    /// <value>The HTTP context.</value>
    public Mock<HttpContextBase> HttpContext { get; private set; }
    /// <summary>
    /// Gets the request.
    /// </summary>
    /// <value>The request.</value>
    public Mock<HttpRequestBase> Request { get; private set; }
    /// <summary>
    /// Gets the response.
    /// </summary>
    /// <value>The response.</value>
    public Mock<HttpResponseBase> Response { get; private set; }
    /// <summary>
    /// Gets the output.
    /// </summary>
    /// <value>The output.</value>
    public StringBuilder Output { get; private set; }
}
/// <summary>
/// Provides Fake Session for use in unit tests
/// </summary>
public class FakeSessionState : HttpSessionStateBase
{
    /// <summary>
    /// backing field for the items in session
    /// </summary>
    private readonly Dictionary<string, object> _items = new Dictionary<string, object>();
    /// <summary>
    /// Gets or sets the <see cref="System.Object"/> with the specified name.
    /// </summary>
    /// <param name="name">the key</param>
    /// <returns>the value in session</returns>
    public override object this[string name]
    {
        get
        {
            return _items.ContainsKey(name) ? _items[name] : null;
        }
        set
        {
            _items[name] = value;
        }
    }
}

你可以进一步添加一些东西,比如HTTP Headers集合,但希望它能演示你能做什么。

使用

var controllerToTest = new HomeController();
var context = new MockHttpContextBase(controllerToTest);
// do stuff that you want to test e.g. something goes into session
Assert.IsTrue(context.HttpContext.Session.Count > 0); 

关于会员提供程序或其他提供程序,您遇到了一些难以测试的东西。我将抽象接口背后提供者的用法,以便在测试依赖该接口的组件时可以为该接口提供一个伪接口。你仍然会在使用提供商的接口的具体实现进行单元测试时遇到麻烦,但是你的里程可能会根据你想要/必须在单元测试和代码覆盖方面走多远而有所不同。

我不知道有什么方法可以做到这一点,因为您的代码不在该进程中,并且需要一个不在aspnet中的主机。(虽然我以前错了,哈哈)

有一个老的HttpSimulator从菲尔哈克,你给了它一个旋转吗?

http://haacked.com/archive/2007/06/19/unit-tests-web-code-without-a-web-server-using-httpsimulator.aspx

您需要为这些服务构建包装器接口。最初的MVC2和MV3 starter项目模板默认是这样做的,但由于某种原因,他们在最新版本中删除了这一点。

您可以尝试找到原始AccountController代码的示例来给您一个起点。他们使用了immembershipservice和IFormsAuthenticationService

模拟会话、上下文等相对简单。

看一下MVCContrib项目(http://mvccontrib.codeplex.com/),因为他们有一个帮助器来创建具有所有各种上下文对象填充的控制器(如HttpContext)。