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

我正在我的控制器中的Session中存储值正在测试的Action。 我已经阅读了几篇关于如何模拟会话的文章,我正在尝试实现Milox 在unit testing中设置httpcontext当前会话的答案。 但是当我钻进Locals | this | base | HttpContext Locals | this | base | HttpContext 当设置Session变量HttpContext.Session["BsAcId"] = vM.BusAcnt.Id; Locals | this | base | HttpContext Sessions仍为null并且测试失败,并带有Null ReferenceexceptionHttpContext.Session["BsAcId"] = vM.BusAcnt.Id;

这是工作生产代码。 vM.BusAcnt.Id返回一个有效的int ,如果我用int值替换它,测试仍然失败,因为Session为null,因此没有值可以存储在其中。

我正在使用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); } 

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

测试:

 [Fact] public void AdminGetBusAcntById() { HttpContext.Current = MockHelpers.FakeHttpContext(); var mockMyDb = MockDbSetup.MockMyDb(); var controller = new BusAcntController(mockMy.Object); var controllerContextMock = new Mock(); controllerContextMock.Setup( x => x.HttpContext.User. IsInRole(It.Is(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有没有破坏这段代码的变化?

解:

实现达林的答案。 我现在有一个会话来编写值(虽然这些值实际上没有被写入,但是为了测试目的不需要)并且测试通过了。

测试:

 [Fact] public void AdminGetBusAcntById() { var mockMyDb = MockDbSetup.MockMyDb(); var controller = new BusAcntController(mockMy.Object); var context = new Mock(); var session = new Mock(); 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); } 

在你的unit testing中你设置了HttpContext.Current = MockHelpers.FakeHttpContext(); 但是ASP.NET MVC根本不使用这个静态属性。 忘掉ASP.NET MVC中的HttpContext.Current 。 它的遗留和unit testing不友好(是的,在您的情况下,您只在unit testing中使用它,但ASP.NET MVC不使用它,这就是您的代码不起作用的原因)。

重点是ASP.NET MVC正在使用HttpContextBase,HttpRequestBase,HttpResponseBase,HttpSessionStateBase等抽象,您可以轻松地在unit testing中进行模拟。

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

 public class HomeController : Controller { public ActionResult Index() { if ((this.User.IsInRole("Admin"))) { return RedirectToAction("Action"); } this.HttpContext.Session["foo"] = "bar"; return View(); } } 

以及如何通过使用Moq模拟所需的抽象来进行相应的unit testing:

 // arrange var controller = new HomeController(); var context = new Mock(); var session = new Mock(); 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")条件,您所要做的就是为模拟身份提供适当的角色。

方式,我将使用MOQ应用MOCKing of Sessions如下。

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

 [TestFixture] public class BaseClass { public Mock controllerContext; public Mock contextBase; public BaseClass() { controllerContext = new Mock(); contextBase = new Mock(); controllerContext.Setup(x => x.HttpContext).Returns(contextBase.Object); controllerContext.Setup(cc => cc.HttpContext.Session["UserId"]).Returns(1); } } 

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

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

 [TestFixture] class ControllerClassTest : BaseClass { } 

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

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

不要忘记,我们必须先声明并初始化控制器。

我希望,它可以帮助你