模拟会话在 MVC 5 中不起作用

Joe*_*Joe 2 c# session mocking asp.net-mvc-5

我将值存储在正在测试的控制器操作的会话中。我已经阅读了几篇关于如何模拟会话的文章,并且我正在尝试实现 Milox 对“在单元测试中设置 httpcontext 当前会话”的回答。但是当我钻取Locals | this | base | HttpContext Sessions仍然为空并且设置会话变量时测试失败并出现空引用异常HttpContext.Session["BsAcId"] = vM.BusAcnt.Id;

这是有效的生产代码。 vM.BusAcnt.Id返回一个有效的int值,如果我用一个int值替换它,测试仍然会失败,因为它Session是空的,因此不能在其中存储任何值。

我正在使用 MVC5、EF6 以及最新版本的 xUnit、Moq 和 Resharper 测试运行程序。

行动:

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);
}
Run Code Online (Sandbox Code Playgroud)

模拟助手:

  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;
    }
  }
Run Code Online (Sandbox Code Playgroud)

测试:

[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);
}
Run Code Online (Sandbox Code Playgroud)

Milox 的代码似乎有意义,但我无法让它工作。我错过了什么吗?MVC5 中是否有更改破坏了此代码?

解决方案:

实施达林的回答。我现在有一个会话来写入值(尽管这些值实际上并未写入其中,但出于测试目的不需要这样做)并且测试通过。

测试:

[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);
}
Run Code Online (Sandbox Code Playgroud)

Dar*_*rov 6

在您的单元测试中您已设置HttpContext.Current = MockHelpers.FakeHttpContext();,但 ASP.NET MVC 根本不使用此静态属性。忘记HttpContext.CurrentASP.NET MVC 中的事情吧。它是遗留的且单元测试不友好(是的,在您的情况下,您仅在单元​​测试中使用它,但 ASP.NET MVC 不使用它,这就是您的代码无法工作的原因)。

重点是 ASP.NET MVC 正在使用 HttpContextBase、HttpRequestBase、HttpResponseBase、HttpSessionStateBase 等抽象,您可以在单元测试中轻松模拟这些抽象。

我们来看一个控制器示例:

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

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

        return View();
    }
}
Run Code Online (Sandbox Code Playgroud)

以及通过使用 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");
...
Run Code Online (Sandbox Code Playgroud)

如果你想进入这个User.IsInRole("Admin")条件,你所要做的就是为被模拟的身份提供适当的角色。