Mocking HttpSessionState в ASP.net для тестирования nunit

Я вижу много дискуссий вокруг HttpSessionState и asp.net MVC. Я пытаюсь написать тесты для приложения asp.net и задаюсь вопросом, можно ли издеваться над HttpSessionState, и если да, то как?

В настоящее время я использую Rhino Mocks и Nunit

Ответ 1

Гилберта,

Возможно, я слишком поздно для вас. Я использую MSpec, но я думаю, что понятия похожи. Мне нужно было издеваться над несколькими компонентами HttpContext в тестируемых контроллерах.

Я начал с этих следующих классов, чтобы макетировать необходимые (для моих целей) компоненты в HttpContextBase. Я перевернул только необходимые предметы внутри классов. Ваши потребности будут различаться в зависимости от макетов, которые вам нужны в контроллере. Это довольно легко добавить макеты по мере необходимости, как только вы понимаете шаблон.

public class MockHttpContext : HttpContextBase 
{

    private readonly HttpRequestBase _request = new MockHttpRequest();
    private readonly HttpServerUtilityBase _server = new MockHttpServerUtilityBase();
    private HttpSessionStateBase _session = new MockHttpSession();

    public override HttpRequestBase Request
    {
        get { return _request; }
    }
    public override HttpServerUtilityBase Server
    {
        get { return _server; }
    }
    public override HttpSessionStateBase Session
    {
        get { return _session; }
    }
}

public class MockHttpRequest : HttpRequestBase
{
    private Uri _url = new Uri("http://www.mockrequest.moc/Controller/Action");

    public override Uri Url
    {
        get { return _url; }
    }
}

public class MockHttpServerUtilityBase : HttpServerUtilityBase
{
    public override string UrlEncode(string s)
    {
        //return base.UrlEncode(s);     
        return s;       // Not doing anything (this is just a Mock)
    }
}


public class MockHttpSession : HttpSessionStateBase
{
    // Started with sample http://stackoverflow.com/questions/524457/how-do-you-mock-the-session-object-collection-using-moq
    // from http://stackoverflow.com/users/81730/ronnblack

    System.Collections.Generic.Dictionary<string, object> _sessionStorage = new   System.Collections.Generic.Dictionary<string,object>();
    public override object this[string name]
    {
        get { return _sessionStorage[name]; }
        set { _sessionStorage[name] = value; }
    }

    public override void Add(string name, object value)
    {
        _sessionStorage[name] = value;
    }
}

Вот как я настраиваю Контекст контроллера для использования mocks (MSpec). Это настройка для фактических тестов на contoller (тесты производятся из этого класса)

public abstract class BlahBlahControllerContext
{
    protected static BlahBlahController controller;

    Establish context = () =>
    {
        controller = new BlahBlahController();
        controller.ControllerContext = new ControllerContext()
        {
            Controller = controller,
            RequestContext = new RequestContext(new MockHttpContext(), new RouteData()),
        };
    };
}

Далее проиллюстрируем тест (спецификация в мире MSpec), который использует макет сессии:

[Subject("ACCOUNT: Retrieve Password")]
public class retrieve_password_displays_retrieve_password2_page_on_success : BlahBlahControllerContext
{
    static ActionResult result;
    static RetrievePasswordModel model;

    Establish context = () =>
    {
        model = new RetrievePasswordModel()
        {
            UserName = "Mike"
        };
    };

    Because of = () =>
    {
        result = controller.RetrievePassword(model);
    };

    It should_return_a_RedirectToRouteResult = () => 
    { 
        result.is_a_redirect_to_route_and().action_name().ShouldEqual("RetrievePassword2"); 
    };

    It session_should_contain_UN_value = () =>
    {
        controller.HttpContext.Session["UN"].ShouldEqual("Mike");
    };

    It session_should_contain_PQ_value = () =>
    {
        controller.HttpContext.Session["PQ"].ShouldEqual("Question");
    };
}

Я понимаю, что это не использует Rhino Mocks. Я надеюсь, что это иллюстрирует принципы, и читатели могут принять его к своим конкретным инструментам и методам.

Ответ 2

Если вам нужно создать экземпляр HttpSessionState для тестов устаревших кодов, вы можете использовать механизм FormatterServices для получения неинициализированного объекта. Чтобы получить его работу, необходимо установить личное поле _container, хотя, как и во внутреннем конструкторе

Пример:

var state = (HttpSessionState) System.Runtime.Serialization
    .FormatterServices.GetUninitializedObject(typeof(HttpSessionState));

var containerFld = typeof(HttpSessionState).GetField(
    "_container", BindingFlags.Instance | BindingFlags.NonPublic);

var itemCollection = new SessionStateItemCollection();
itemCollection["element"] = 1;

containerFld.SetValue(
    state,
    new HttpSessionStateContainer(
        "1",
        itemCollection,
        new HttpStaticObjectsCollection(),
        900,
        true,
        HttpCookieMode.UseCookies,
        SessionStateMode.InProc,
        false
    )
);

Ответ 3

просмотрите классы HttpSessionStateBase и HttpSessionStateWrapper в System.Web.Abstractions. HttpSessionStateBase - это абстрактный класс, из которого наследуется HttpSessionState, а HttpSessionStateWrapper используется для обертывания закрытого класса в абстрактном классе, который вы можете затем высмеять в своих тестах.

Многие классы System.Web запечатаны (например, HttpSessionState), так что это настоящая боль, чтобы протестировать ваш код, когда у вас есть методы и классы, которые взаимодействуют с ними. Один шаблон, который я хотел бы использовать, чтобы обойти это, выглядит следующим образом:

public void DoSomething(HttpSessionState state)
{
  // take this HttpSeassionState and create an abstract HttpSessionStateBase
  // instance
  DoSomething(new HttpSessionStateWrapper(state));
}

internal void DoSomething(HttpSessionStateBase state)
{
  // my actual logic for working with the session state
}

Открытый метод трудно проверить, потому что HttpSessionState запечатан, и вы не можете его издеваться. Однако внутренний метод работает с экземпляром HttpSessionStateBase, который вы можете высмеять. Обратите внимание, что я помечен как внутренний, потому что я не хочу, чтобы внешний мир мог получить доступ к этому методу. Тем не менее, я хочу, чтобы мои тесты имели доступ к этому, поэтому я буду изменять свой AssemblyInfo.cs, чтобы включить что-то вроде этого:

[assembly: InternalsVisibleTo("Vendor.Utilities.Tests")] 

Наконец, мой тест для этого будет выглядеть примерно так:

[Test]
public void Test_DoSomething()
{
  HttpSessionStateBase state = MockRepository.PartialMock<HttpSessionStateBase>();
  state.Expect(s => ...);

  MyClass.DoSomething(state);

  state.VerifyAllExpectations();
}

Надеюсь, что это поможет. Удачи!

Ответ 4

Это то, что я составил на основе других вкладов...

открытый класс MockWebContext   {

    public Mock<RequestContext> RoutingRequestContext { get; private set; }
    public Mock<HttpContextBase> Http { get; private set; }
    public Mock<HttpServerUtilityBase> Server { get; private set; }
    public Mock<HttpResponseBase> Response { get; private set; }
    public Mock<HttpRequestBase> Request { get; private set; }
    public Mock<HttpSessionStateBase> Session { get; private set; }
    public Mock<ActionExecutingContext> ActionExecuting { get; private set; }
    public HttpCookieCollection Cookies { get; private set; }

    private IDictionary items;

    public MockWebContext()
    {
        RoutingRequestContext = new Mock<RequestContext>(MockBehavior.Loose);
        ActionExecuting = new Mock<ActionExecutingContext>(MockBehavior.Loose);
        Http = new Mock<HttpContextBase>(MockBehavior.Loose);
        Server = new Mock<HttpServerUtilityBase>(MockBehavior.Loose);
        Response = new Mock<HttpResponseBase>(MockBehavior.Loose);
        Request = new Mock<HttpRequestBase>(MockBehavior.Loose);
        Session = new Mock<HttpSessionStateBase>(MockBehavior.Loose);
        Cookies = new HttpCookieCollection();

        items = new Dictionary<string, object>();

        RoutingRequestContext.SetupGet(c => c.HttpContext).Returns(Http.Object);
        ActionExecuting.SetupGet(c => c.HttpContext).Returns(Http.Object);
        Http.SetupGet(c => c.Request).Returns(Request.Object);
        Http.SetupGet(c => c.Response).Returns(Response.Object);
        Http.SetupGet(c => c.Server).Returns(Server.Object);
        Http.SetupGet(c => c.Session).Returns(Session.Object);
        Http.SetupGet(c => c.Items).Returns(items);
        Request.Setup(c => c.Cookies).Returns(Cookies);
        Request.Setup(c => c.RequestContext).Returns(RoutingRequestContext.Object);
        Response.Setup(c => c.Cookies).Returns(Cookies);
        Session.Setup(c => 
            c.Add(It.IsAny<string>(), It.IsAny<object>())                
            ).Callback((string key, object value)=> items.Add(key, value));

        Session.Setup(c =>
          c.Remove(It.IsAny<string>())
          ).Callback((string key) => items.Remove(key));


        Session.Setup(c =>
          c.Clear()
          ).Callback(() => items.Clear());

        Session.Setup(c =>
        c[It.IsAny<string>()]
        ).Returns((string key)=> items[key]);


    }

}