Nunit Testing MVC Site - PullRequest
       5

Nunit Testing MVC Site

6 голосов
/ 19 сентября 2011

Я столкнулся с небольшой проблемой, пытаясь провести модульное тестирование сайта MVC, который у меня есть: мне требуется много среды ASP.NET для работы (генерация httpcontexts, сеансов, файлов cookie, членства и т. Д.) чтобы полностью проверить все.

Даже для того, чтобы протестировать некоторые из менее передовых вещей, требуются членство для правильной работы, и было очень придирчиво, чтобы все это было подделано вручную.

Есть ли способ ускорить пул приложений внутри тестов NUnit? Это кажется самым простым способом.

Ответы [ 4 ]

4 голосов
/ 19 сентября 2011

Если написано правильно, вам не нужно иметь реальный контекст, реальный сеанс, куки-файлы и т. Д. Среда MVC по умолчанию предоставляет HttpContext, который можно смоделировать / заглушить.Я бы порекомендовал использовать фальшивый фреймворк, такой как Moq или Rhino Mocks, и создать класс MockHttpContext, который создает фиктивный контекст со всеми свойствами, с которыми вам нужно протестировать настройку.Вот макет HttpContext, который использует 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;
        }
    }
}

Есть несколько вещей, которые вы можете добавить, например, коллекцию HTTP-заголовков, но, надеюсь, это демонстрирует, что вы можете сделать.

Для использования

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

Что касается провайдеров членства или других провайдеров, вы столкнулись с чем-то, что может быть трудно проверить.Я хотел бы абстрагироваться от использования провайдера за интерфейсом таким образом, чтобы вы могли предоставить подделку для интерфейса при тестировании компонента, который на него опирается.Однако у вас все еще будут проблемы с модульным тестированием конкретной реализации интерфейса, который использует провайдер, но ваш пробег может варьироваться в зависимости от того, насколько далеко вы хотите / должны идти в отношении модульного тестирования и покрытия кода.

2 голосов
/ 19 сентября 2011

Я не знаю, как это сделать, поскольку ваш код не находится в этом процессе и требует хоста, который не находится в aspnet.(Хотя раньше я ошибался, хаха)

Вот старый HttpSimulator от Фила Хаака, вы уже подумали об этом?

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

0 голосов
/ 19 сентября 2011

Посмотрите на проект MVCContrib (http://mvccontrib.codeplex.com/), поскольку у них есть помощник для создания контроллеров, в которых заполнены все различные контекстные объекты (например, HttpContext).

0 голосов
/ 19 сентября 2011

Вам необходимо создать интерфейсы-оболочки для этих сервисов. Исходные шаблоны стартового проекта MVC2 и MV3 делали это по умолчанию, но по некоторым причинам они отказались от этого в последних версиях.

Вы можете попытаться найти образцы исходного кода AccountController, чтобы получить стартовую позицию. Они использовали IMembershipService и IFormsAuthenticationService

Относительно просто смоделировать сеанс, контекст и т. Д.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...