【问题标题】:Mocking Session not working in MVC 5模拟会话在 MVC 5 中不起作用
【发布时间】:2014-05-10 21:56:32
【问题描述】:

我将值存储在正在测试的控制器 Action 中的 Session 中。我已经阅读了几篇关于如何模拟会话的文章,并且我正在尝试实现 Milox 对Setting the httpcontext current session in unit test 的回答。但是当我深入到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);
}

模拟助手:

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

测试:

[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 的代码似乎有道理,但我无法让它工作。 我错过了什么吗? MVC5 中是否存在破坏此代码的更改?

解决方案:

达林回答的实施。我现在有一个 Session 来写入值(尽管这些值实际上并没有被写入其中,但这不是测试目的所必需的)并且测试通过了。

测试:

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

【问题讨论】:

    标签: c# session mocking asp.net-mvc-5


    【解决方案1】:

    在您的单元测试中,您设置了HttpContext.Current = MockHelpers.FakeHttpContext();,但 ASP.NET MVC 根本不使用此静态属性。忘记 ASP.NET MVC 中的 HttpContext.Current。它是遗留的并且单元测试不友好(是的,在您的情况下,您仅在单元​​测试中使用它,但 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();
        }
    }
    

    以及通过使用 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");
    ...
    

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

    【讨论】:

    • 谢谢。你一如既往地把它钉牢了。请参阅 SOLUTION: in OP 以在我的测试中实现。
    • 嗨达林,你写了“忘记 ASP.NET MVC 中的 HttpContext.Current”,那么你如何访问,比如说,从自定义类(即在控制器外部)的 Session 对象。例如,它的等价物是什么(在控制器之外):HttpContext.Current.Session["MyField"]
    【解决方案2】:

    我将使用 MOQ 应用模拟会话的方式如下。

    我会在 UnitTests 项目中创建一个基类。结构将是

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

    请参阅:我在最后一行返回 1 作为 UserId 的会话值。您可以根据需要更改它。

    为了便于参考,我将我的 TestClass 命名为“ControllerClassTest”。所以我会像这样用 BaseClass 继承 ControllerClassTest

    [TestFixture]
    class ControllerClassTest : BaseClass
    {
    }
    

    然后,在我的测试类中,我会像这样在 Setup 方法中初始化 ControllerContext

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

    别忘了,我们必须先声明和初始化控制器。

    希望对你有帮助

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-07-07
      • 2015-06-03
      • 2015-09-01
      • 1970-01-01
      • 2015-06-30
      • 2016-07-11
      • 2017-10-14
      • 1970-01-01
      相关资源
      最近更新 更多