【发布时间】:2010-09-15 15:16:18
【问题描述】:
我在这里阅读了一些答案:测试视图和控制器以及模拟,但我仍然无法弄清楚如何测试读取和设置会话值(或任何其他基于上下文的 ASP.NET MVC 控制器)变量。) 如何为我的测试方法提供(会话)上下文?是在嘲讽答案吗?有人有例子吗? 基本上,我想在调用控制器方法并让控制器使用该会话之前伪造一个会话。有什么想法吗?
【问题讨论】:
标签: asp.net-mvc unit-testing mocking session
我在这里阅读了一些答案:测试视图和控制器以及模拟,但我仍然无法弄清楚如何测试读取和设置会话值(或任何其他基于上下文的 ASP.NET MVC 控制器)变量。) 如何为我的测试方法提供(会话)上下文?是在嘲讽答案吗?有人有例子吗? 基本上,我想在调用控制器方法并让控制器使用该会话之前伪造一个会话。有什么想法吗?
【问题讨论】:
标签: asp.net-mvc unit-testing mocking session
查看 Stephen Walther 关于伪造控制器上下文的帖子:
ASP.NET MVC Tip #12 – Faking the Controller Context
[TestMethod]
public void TestSessionState()
{
// Create controller
var controller = new HomeController();
// Create fake Controller Context
var sessionItems = new SessionStateItemCollection();
sessionItems["item1"] = "wow!";
controller.ControllerContext = new FakeControllerContext(controller, sessionItems);
var result = controller.TestSession() as ViewResult;
// Assert
Assert.AreEqual("wow!", result.ViewData["item1"]);
// Assert
Assert.AreEqual("cool!", controller.HttpContext.Session["item2"]);
}
【讨论】:
ASP.NET MVC 框架对模拟不是很友好(或者更确切地说,需要太多设置才能正确模拟,并且在测试时造成太多摩擦,恕我直言),因为它使用抽象基类而不是接口。我们很幸运地为每个请求和基于会话的存储编写了抽象。我们将这些抽象保持得很轻,然后我们的控制器依赖于这些抽象来进行每个请求或每个会话的存储。
例如,以下是我们管理表单身份验证内容的方式。我们有一个 ISecurityContext:
public interface ISecurityContext
{
bool IsAuthenticated { get; }
IIdentity CurrentIdentity { get; }
IPrincipal CurrentUser { get; set; }
}
具体实现如下:
public class SecurityContext : ISecurityContext
{
private readonly HttpContext _context;
public SecurityContext()
{
_context = HttpContext.Current;
}
public bool IsAuthenticated
{
get { return _context.Request.IsAuthenticated; }
}
public IIdentity CurrentIdentity
{
get { return _context.User.Identity; }
}
public IPrincipal CurrentUser
{
get { return _context.User; }
set { _context.User = value; }
}
}
【讨论】:
在 MVC RC 1 中,ControllerContext 包装了 HttpContext 并将其作为属性公开。这使得模拟更容易。要使用 Moq 模拟会话变量,请执行以下操作:
var controller = new HomeController();
var context = MockRepository.GenerateStub<ControllerContext>();
context.Expect(x => x.HttpContext.Session["MyKey"]).Return("MyValue");
controller.ControllerContext = context;
更多详情请见Scott Gu's post。
【讨论】:
我发现嘲笑是相当容易的。这是一个使用 moq 模拟 httpContextbase(包含请求、会话和响应对象)的示例。
[TestMethod]
public void HowTo_CheckSession_With_TennisApp() {
var request = new Mock<HttpRequestBase>();
request.Expect(r => r.HttpMethod).Returns("GET");
var httpContext = new Mock<HttpContextBase>();
var session = new Mock<HttpSessionStateBase>();
httpContext.Expect(c => c.Request).Returns(request.Object);
httpContext.Expect(c => c.Session).Returns(session.Object);
session.Expect(c => c.Add("test", "something here"));
var playerController = new NewPlayerSignupController();
memberController.ControllerContext = new ControllerContext(new RequestContext(httpContext.Object, new RouteData()), playerController);
session.VerifyAll(); // function is trying to add the desired item to the session in the constructor
//TODO: Add Assertions
}
希望对您有所帮助。
【讨论】:
Scott Hanselman 有一篇关于如何使用 MVC create a file upload quickapp 的帖子并讨论了 moking 并专门解决了“如何模拟不适合模拟的事物”。
【讨论】:
我使用了以下解决方案 - 制作一个我所有其他控制器都继承自的控制器。
public class TestableController : Controller
{
public new HttpSessionStateBase Session
{
get
{
if (session == null)
{
session = base.Session ?? new CustomSession();
}
return session;
}
}
private HttpSessionStateBase session;
public class CustomSession : HttpSessionStateBase
{
private readonly Dictionary<string, object> dictionary;
public CustomSession()
{
dictionary = new Dictionary<string, object>();
}
public override object this[string name]
{
get
{
if (dictionary.ContainsKey(name))
{
return dictionary[name];
} else
{
return null;
}
}
set
{
if (!dictionary.ContainsKey(name))
{
dictionary.Add(name, value);
}
else
{
dictionary[name] = value;
}
}
}
//TODO: implement other methods here as needed to forefil the needs of the Session object. the above implementation was fine for my needs.
}
}
然后使用代码如下:
public class MyController : TestableController { }
【讨论】:
因为 HttpContext 是静态的,所以我使用 Typemock Isolator 来模拟它,Typemock 还有一个为 ASP.NET unit testing 定制的 Add-in,称为 Ivonna。
【讨论】: