web-dev-qa-db-fra.com

Comment se moquer de la collection d'objets de session en utilisant Moq

J'utilise la classe MvcMockHelper de shanselmann pour simuler du contenu HttpContext à l'aide de Moq, mais le problème que je rencontre est de pouvoir affecter quelque chose à mon objet de session simulé dans mon contrôleur MVC, puis de pouvoir lire cette même valeur dans mon unité de test à des fins de vérification.

Ma question est la suivante: comment affecter une collection de stockage à l'objet de session simulé pour permettre à un code tel que session ["UserName"] = "foo" de conserver la valeur "foo" et de la conserver dans le test unitaire.

42
rayray2030

J'ai commencé avec MVCMockHelper de Scott Hanselman, puis j'ai ajouté une petite classe et apporté les modifications indiquées ci-dessous pour permettre au contrôleur d'utiliser Session normalement et au test unitaire de vérifier les valeurs définies par le contrôleur.

/// <summary>
/// A Class to allow simulation of SessionObject
/// </summary>
public class MockHttpSession : HttpSessionStateBase
{
    Dictionary<string, object> m_SessionStorage = new Dictionary<string, object>();

    public override object this[string name]
    {
        get { return m_SessionStorage[name]; }
        set { m_SessionStorage[name] = value; }
    }
}

//In the MVCMockHelpers I modified the FakeHttpContext() method as shown below
public static HttpContextBase FakeHttpContext()
{
    var context = new Mock<HttpContextBase>();
    var request = new Mock<HttpRequestBase>();
    var response = new Mock<HttpResponseBase>();
    var session = new MockHttpSession();
    var server = new Mock<HttpServerUtilityBase>();

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

    return context.Object;
}

//Now in the unit test i can do
AccountController acct = new AccountController();
acct.SetFakeControllerContext();
acct.SetBusinessObject(mockBO.Object);

RedirectResult results = (RedirectResult)acct.LogOn(userName, password, rememberMe, returnUrl);
Assert.AreEqual(returnUrl, results.Url);
Assert.AreEqual(userName, acct.Session["txtUserName"]);
Assert.IsNotNull(acct.Session["SessionGUID"]);

Ce n'est pas parfait mais ça marche assez pour les tests.

60
RonnBlack

En utilisant Moq 3.0.308.2, voici un exemple de la configuration de mon contrôleur de compte dans mon test unitaire:

    private AccountController GetAccountController ()
    {
      .. setup mocked services..

      var accountController = new AccountController (..mocked services..);

      var controllerContext = new Mock<ControllerContext> ();
      controllerContext.SetupGet(p => p.HttpContext.Session["test"]).Returns("Hello World");
      controllerContext.SetupGet(p => p.HttpContext.User.Identity.Name).Returns(_testEmail);
      controllerContext.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true);
      controllerContext.SetupGet(p => p.HttpContext.Response.Cookies).Returns(new HttpCookieCollection ());

      controllerContext.Setup (p => p.HttpContext.Request.Form.Get ("ReturnUrl")).Returns ("sample-return-url");
      controllerContext.Setup (p => p.HttpContext.Request.Params.Get ("q")).Returns ("sample-search-term");

      accountController.ControllerContext = controllerContext.Object;

      return accountController;
    }

alors dans votre méthode de contrôleur ce qui suit devrait retourner "Hello World"

string test = Session["test"].ToString ();
32
Todd Smith

J'ai créé une maquette un peu plus élaborée que la réponse publiée par @RonnBlack

public class HttpSessionStateDictionary : HttpSessionStateBase
{
    private readonly NameValueCollection keyCollection = new NameValueCollection();

    private readonly Dictionary<string, object> _values = new Dictionary<string, object>();

    public override object this[string name]
    {
        get { return _values.ContainsKey(name) ? _values[name] : null; }
        set { _values[name] = value; keyCollection[name] = null;}
    }

    public override int CodePage
    {
        get { throw new NotImplementedException(); }
        set { throw new NotImplementedException(); }
    }

    public override HttpSessionStateBase Contents
    {
        get { throw new NotImplementedException(); }
    }

    public override HttpCookieMode CookieMode
    {
        get { throw new NotImplementedException(); }
    }

    public override int Count
    {
        get { return _values.Count; }
    }

     public override NameObjectCollectionBase.KeysCollection Keys
{
    get { return keyCollection.Keys; }
}

    public Dictionary<string, object> UnderlyingStore
    {
        get { return _values; }
    }

    public override void Abandon()
    {
        _values.Clear();
    }

    public override void Add(string name, object value)
    {
        _values.Add(name, value);
    }

    public override void Clear()
    {
        _values.Clear();
    }

    public override void CopyTo(Array array, int index)
    {
        throw new NotImplementedException();
    }

    public override bool Equals(object obj)
    {
        return _values.Equals(obj);
    }

    public override IEnumerator GetEnumerator()
    {
        return _values.GetEnumerator();
    }

    public override int GetHashCode()
    {
        return (_values != null ? _values.GetHashCode() : 0);
    }

    public override void Remove(string name)
    {
        _values.Remove(name);
    }

    public override void RemoveAll()
    {
        _values.Clear();
    }

    public override void RemoveAt(int index)
    {
        throw new NotImplementedException();
    }

    public override string ToString()
    {
        return _values.ToString();
    }

    public bool Equals(HttpSessionStateDictionary other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Equals(other._values, _values);
    }
}
3
Chris Marisic

Je viens de trouver un bel exemple de la façon dont l'équipe Oxite simule son HttpSessionState et gère une collection SessionStateItemCollection au sein de ce faux. Cela devrait fonctionner aussi bien qu'un moq dans mon cas.

MODIFIER:

L’URL de cet exemple est http://oxite.codeplex.com/sourcecontrol/changeset/view/33871?projectName=oxite#388065

2
rayray2030

Merci @RonnBlack pour votre solution! Dans mon cas, j'ai gardé cette exception parce que Session.SessionID était null:

System.NotImplementedException was unhandled by user code
  HResult=-2147467263
  Message=The method or operation is not implemented.
  Source=System.Web
  StackTrace:
       at System.Web.HttpSessionStateBase.get_SessionID()

Pour résoudre ce problème, j'implémente le code de @RonnBlack de cette façon en utilisant le Moq Mock<HttpSessionStateBase> au lieu de sa MockHttpSession:

    private readonly MyController controller = new MyController();

    [TestFixtureSetUp]
    public void Init()
    {
        var session = new Mock<HttpSessionStateBase>();
        session.Setup(s => s.SessionID).Returns(Guid.NewGuid().ToString());
        var request = new Mock<HttpRequestBase>();
        var response = new Mock<HttpResponseBase>();
        var server = new Mock<HttpServerUtilityBase>();
        // Not working - IsAjaxRequest() is static extension method and cannot be mocked
        // request.Setup(x => x.IsAjaxRequest()).Returns(true /* or false */);
        // use this
        request.SetupGet(x => x.Headers).Returns(
            new System.Net.WebHeaderCollection
            {
                {"X-Requested-With", "XMLHttpRequest"}
            });

        var context = new Mock<HttpContextBase>();
        //context
        context.Setup(ctx => ctx.Request).Returns(request.Object);
        context.Setup(ctx => ctx.Response).Returns(response.Object);
        context.Setup(ctx => ctx.Session).Returns(session.Object);
        context.Setup(ctx => ctx.Server).Returns(server.Object);
        context.SetupGet(x => x.Request).Returns(request.Object);
        context.SetupGet(p => p.Request.Url).Returns(new Uri("http://www.mytesturl.com"));
        var queryString = new NameValueCollection { { "code", "codeValue" } };
        context.SetupGet(r => r.Request.QueryString).Returns(queryString);

        controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);
    }

Pour plus de détails, consultez http://weblogs.asp.net/gunnarpeipman/using-moq-to-mock-asp-net-mvc-httpcontextbase

0
user8128167

Je pense que vous pouvez définir une attente sur la maquette avec une valeur spécifique qui devrait être restituée. Les simulacres ne sont pas utilisés comme des faux mais plutôt comme des choses sur lesquelles vous pouvez affirmer un comportement.

Il semble que vous cherchiez en réalité un adaptateur que vous pouvez intégrer à la session et que vous pouvez fournir une implémentation différente lors des tests et pendant l'exécution, il renverrait des éléments HttpContext Session?

Est-ce que ça a du sens?

0
Sean Chambers