Nunit Testing MVC Site

余生长醉 提交于 2019-12-23 09:29:31

问题


I've run into a bit of an issue trying to unit test an MVC site I have: I require a lot of the ASP.NET environment to be running (generation of httpcontexts, sessions, cookies, memberships, etc.) to fully test everything.

Even to test some of the less front end stuff needs memberships in order to properly work, and it's been finicky to get this all spoofed by hand.

Is there a way to spin up an application pool inside of NUnit tests? That seems like the easiest way.


回答1:


I'm not aware of a way to do that since your code isn't in that process and requires a host that isn't in aspnet either. (I've been wrong before though haha)

Theres an older HttpSimulator from Phil Haack, have you given that a whirl?

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




回答2:


If written properly, you shouldn't need to have a real context, real session, cookies, etc. The MVC framework by default provides a HttpContext that can be mocked/stubbed. I'd recommend using a mocking framework like Moq or Rhino Mocks and creating a MockHttpContext class that creates a mock context with all of the properties that you need to test against set up. Here's a mock HttpContext that uses Moq

/// <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;
        }
    }
}

There's a few things that you could add further like a HTTP Headers collection, but hopefully it demonstrates what you can do.

To use

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); 

With regards to Membership providers or other providers, you've hit on something that can be hard to test. I would abstract the usage of the provider behind an interface such that you can provide a fake for the interface when testing a component that relies on it. You'll still have trouble unit testing the concrete implementation of the interface that uses the provider however but your mileage may vary as to how far you want/have to go with regards to unit testing and code coverage.




回答3:


You need to build wrapper interfaces for those services. The original MVC2 and MV3 starter project templates did this by default, but for some reason they dropped that in the latest versions.

You can try to find samples of the original AccountController code to give you a starting place. They used IMembershipService and IFormsAuthenticationService

It's relatively straightforward to mock session, context, etc..




回答4:


Take a look at the MVCContrib project (http://mvccontrib.codeplex.com/) as they have a helper for creating controllers that have all the various contextual objects populated (like HttpContext).



来源:https://stackoverflow.com/questions/7476041/nunit-testing-mvc-site

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!