我希望在我参与的项目中使用单元测试。该解决方案有一个UI层,一个域逻辑层和数据库库,它们全部分隔成物理上分隔的dll,因此它现在非常标准。
在Domain层中,我们有一个使用
的类new HttpContextWrapper(HttpContext.Current)
获取当前请求的上下文,以编译动态菜单的Url。正如预期的那样,在Web环境中运行时一切正常,因为总是设置HttpContext.Current。
然而,当我对控制器进行单元测试时,我点击了这行代码并获得了Null引用异常。
经过一些研究后,我有很多文章建议使用Mock创建一个Fake Http Context并在创建控制器时设置它,如下所示:
var httpContext = FakeHttpContext();
ControllerContext context = new ControllerContext(new RequestContext(httpContext, new RouteData()), controller);
controller.ControllerContext = context;
但是这仍然没有改变HttpContxt.Current,我仍然得到一个空引用异常。
我做错了什么/如何解决此异常?
答案 0 :(得分:1)
你看过MvcContrib TestHelper了吗?我从未使用它,但我看到其他人已经成功了。这是一篇包含further examples的博文。
答案 1 :(得分:1)
您可以使用以下代码创建HttpContext.Current:
[SetUp]
public void Test_SetUp()
{
var context = CreateHttpContext("index.aspx", "http://tempuri.org/index.aspx", null);
var result = RunInstanceMethod(Thread.CurrentThread, "GetIllogicalCallContext", new object[] { });
SetPrivateInstanceFieldValue(result, "m_HostContext", context);
}
私人方法:
private static HttpContext CreateHttpContext(string fileName, string url, string queryString)
{
var sb = new StringBuilder();
var sw = new StringWriter(sb);
var hres = new HttpResponse(sw);
var hreq = new HttpRequest(fileName, url, queryString);
var httpc = new HttpContext(hreq, hres);
return httpc;
}
private static object RunInstanceMethod(object source, string method, object[] objParams)
{
var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
var type = source.GetType();
var m = type.GetMethod(method, flags);
if (m == null)
{
throw new ArgumentException(string.Format("There is no method '{0}' for type '{1}'.", method, type));
}
var objRet = m.Invoke(source, objParams);
return objRet;
}
public static void SetPrivateInstanceFieldValue(object source, string memberName, object value)
{
var field = source.GetType().GetField(memberName, BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
if (field == null)
{
throw new ArgumentException(string.Format("Could not find the private instance field '{0}'", memberName));
}
field.SetValue(source, value);
}
答案 2 :(得分:1)
您应该在域类中使用HttpContextBase
,我建议将该域类注入您的控制器,因此在单元测试期间,您可以向被测控制器提供带有虚假httpcontext的域类实例
实施例
public class DomainClass
{
private readonly HttpContextBase _httpContextBase;
// unit testing
public DomainClass(HttpContextBase httpContextBase)
{
_httpContextBase = httpContextBase;
}
public DomainClass()
{
_httpContextBase = new HttpContextWrapper(HttpContext.Current);
}
}
答案 3 :(得分:0)
您不能以这种方式使用HttpContext.Current。在我之前的一次演出中,为了解释这一点,我们必须创建一个单独的包装类并访问该属性。
使用静力学是单元测试中非常常见的问题。
根据您的更新请求:
public static class HttpContextContainer
{
private static HttpContext _ctx;
public static HttpContext Current
{
get
{
if (_ctx == null) _ctx = HttpContext.Current;
return _ctx;
}
set
{
_ctx = value;
}
}
}
然后在您的使用中,您可能会执行以下操作:
new HttpContextWrapper(HttpContextContainer.Current)
和
var httpContext = FakeHttpContext();
HttpContextContainer.Current = httpContext;
ControllerContext context = new ControllerContext(new RequestContext(httpContext, new RouteData()), controller);
controller.ControllerContext = context;
答案 4 :(得分:0)
在Domain层中,我们有一个使用new的类 HttpContextWrapper(HttpContext.Current)
我认为您可能遇到设计问题。
为什么域层需要了解GUI内容?
您可能希望在控制器构造函数上创建一个参数来接受IHttpContextWrapper,这样您就可以传入模拟的包装器。像这样:
public MyController()
:this(new HttpContextWrapper())
{
....
}
public MyController(IHttpContextWrapper contextWrapper)
{
....
}
看起来像
的测试var context = new Mock<IHttpContextWrapper>();
// setup context
var controller = new MyController(context.Object);
....
然后更改控制器以传递域层所需的任何信息。
答案 5 :(得分:0)
警示故事
警惕使用 MS Fakes 来弥补 HttpContext.Current { get; }
调用 HttpContext.Current { set; }
会成功,但设置的值将不可用。
我们有人在基于 MSTest 的测试中使用 [TestInitialize]
进行了设置。
ShimHttpContext.CurrentGet = () => new ShimHttpContext()
{
SessionGet = () => new ShimHttpSessionState()
{
ItemGetString = (name) =>
{
return null;
}
}
};