Mocking Session not working in MVC 5

廉价感情. 提交于 2020-01-03 05:23:06

问题


I'm storing values in the Session in my controller Action being tested. I've read several articles on how to mock a session and I'm trying to implement Milox's answer to Setting the httpcontext current session in unit test. But when I drill into Locals | this | base | HttpContext Sessions is still null and the test fails with a Null Reference exception when setting the Session variable HttpContext.Session["BsAcId"] = vM.BusAcnt.Id;

This is working production code. vM.BusAcnt.Id returns a valid int and if I substitute it with an int value the test still fails because the Session is null and therefore no value can be stored in it.

I'm using MVC5, EF6, and the latest versions of xUnit, Moq and the Resharper test runner.

Action:

public ActionResult Details(int id)
{
  var vM = new BusAcntVm();
  vM.BusAcnt = _db.BusAcnts.FirstOrDefault(bA => bA.Id == id);

  if ((User.IsInRole("Admin"))) return RedirectToAction("Action");

  HttpContext.Session["BsAcId"] = vM.BusAcnt.Id;

  return View(vM);
}

MockHelpers:

  public static class MockHelpers
  {
    public static HttpContext FakeHttpContext()
    {
      var httpRequest = new HttpRequest("", "http://localhost/", "");
      var stringWriter = new StringWriter();
      var httpResponce = new HttpResponse(stringWriter);
      var httpContext = new HttpContext(httpRequest, httpResponce);

      var sessionContainer = new HttpSessionStateContainer("id", new SessionStateItemCollection(),
                                              new HttpStaticObjectsCollection(), 10, true,
                                              HttpCookieMode.AutoDetect,
                                              SessionStateMode.InProc, false);

      httpContext.Items["AspSession"] = typeof(HttpSessionState).GetConstructor(
                                  BindingFlags.NonPublic | BindingFlags.Instance,
                                  null, CallingConventions.Standard,
                                  new[] { typeof(HttpSessionStateContainer) },
                                  null)
                          .Invoke(new object[] { sessionContainer });

      return httpContext;
    }
  }

Test:

[Fact] 
public void AdminGetBusAcntById()
{
  HttpContext.Current = MockHelpers.FakeHttpContext();
  var mockMyDb = MockDbSetup.MockMyDb();
  var controller = new BusAcntController(mockMy.Object);
  var controllerContextMock = new Mock<ControllerContext>();
  controllerContextMock.Setup( x => x.HttpContext.User.
    IsInRole(It.Is<string>(s => s.Equals("Admin")))).Returns(true);
  controller.ControllerContext = controllerContextMock.Object;

  var viewResult = controller.Details(1) as ViewResult;
  var model = viewResult.Model as BusAcntVm;

  Assert.NotNull(model);
  Assert.Equal("Company 1", model.CmpnyName);
}

Milox's code seems to make sense but I can't get it to work. Have I missed something? Is there a change in MVC5 that breaks this code?

SOLUTION:

Implementation of Darin's answer. I now have a Session to write the values against (though the values don't actually get written into it, but that's not needed for the purpose of testing) and the test passes.

Test:

[Fact] 
public void AdminGetBusAcntById()
{
  var mockMyDb = MockDbSetup.MockMyDb();
  var controller = new BusAcntController(mockMy.Object);
  var context = new Mock<HttpContextBase>();
  var session = new Mock<HttpSessionStateBase>();
  var user = new GenericPrincipal(new GenericIdentity("fakeUser"), new[] { "Admin" });
  context.Setup(x => x.User).Returns(user);
  context.Setup(x => x.Session).Returns(session.Object);
  var requestContext = new RequestContext(context.Object, new RouteData());
  controller.ControllerContext = new ControllerContext(requestContext, controller);


  var viewResult = controller.Details(1) as ViewResult;
  var model = viewResult.Model as BusAcntVm;

  Assert.NotNull(model);
  Assert.Equal("Company 1", model.CmpnyName);
}

回答1:


In your unit test you have set HttpContext.Current = MockHelpers.FakeHttpContext(); but ASP.NET MVC doesn't use this static property at all. Forget about HttpContext.Current in ASP.NET MVC. It's legacy and unit testing unfriendly (yes, in your case you are using it only inside your unit test, but ASP.NET MVC doesn't use it and is the reason why your code doesn't work).

The whole point is that ASP.NET MVC is working with abstractions such as HttpContextBase, HttpRequestBase, HttpResponseBase, HttpSessionStateBase, ... that you could easily mock in your unit test.

Let's take an example controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        if ((this.User.IsInRole("Admin")))
        {
            return RedirectToAction("Action");
        }

        this.HttpContext.Session["foo"] = "bar";

        return View();
    }
}

and how a corresponding unit test might look like by mocking the required abstractions using Moq:

// arrange
var controller = new HomeController();
var context = new Mock<HttpContextBase>();
var session = new Mock<HttpSessionStateBase>();
var user = new GenericPrincipal(new GenericIdentity("john"), new[] { "Contributor" });
context.Setup(x => x.User).Returns(user);            
context.Setup(x => x.Session).Returns(session.Object);
var requestContext = new RequestContext(context.Object, new RouteData());
controller.ControllerContext = new ControllerContext(requestContext, controller);

// act
var actual = controller.Index();

// assert
session.VerifySet(x => x["foo"] = "bar");
...

And if you wanted to enter the User.IsInRole("Admin") condition, all you have to do is provide the proper role to the mocked identity.




回答2:


The way, I would apply Mocking of Sessions using MOQ is as follows.

I would create a base class in UnitTests Project. Structure would be

[TestFixture]
public class BaseClass
{
    public Mock<ControllerContext> controllerContext;
    public Mock<HttpContextBase> contextBase;

    public BaseClass()
    {
        controllerContext = new Mock<ControllerContext>();
        contextBase = new Mock<HttpContextBase>();

        controllerContext.Setup(x => x.HttpContext).Returns(contextBase.Object);
        controllerContext.Setup(cc => cc.HttpContext.Session["UserId"]).Returns(1);
    }
}

Please see : I am returning 1 as session value for UserId in the last line. You can change it as per the requirement.

For easy reference, I would name my TestClass as "ControllerClassTest". So I would inherit ControllerClassTest with BaseClass like this

[TestFixture]
class ControllerClassTest : BaseClass
{
}

Then, In my Test Class, I would initialize ControllerContext within Setup method like this

[SetUp]
public void Setup()
{
    controller.ControllerContext = controllerContext.Object;
}

Not to forget, that we have to declare and initialize controller first.

I hope, it helps you



来源:https://stackoverflow.com/questions/23586765/mocking-session-not-working-in-mvc-5

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