在单元测试中设置 HttpContext.Current.Application 键
本文关键字:Current Application HttpContext 设置 单元测试 | 更新日期: 2023-09-27 18:35:26
我有一个方法,它在内部使用静态HttpContext.Current.Application
来存储键值对,然后在整个应用程序中检索它。
现在,我想做的是对该方法进行单元测试。我目前拥有的是这个:
private const string Foo = "foobar";
[TestInitialize]
public void InitializeContext()
{
HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
HttpContext.Current.Application["fooKey"] = Foo;
}
[TestCleanup]
public void DisposeContext()
{
HttpContext.Current = null;
}
一切都很好,但是当我尝试在TestMethod
中转换该值时,该值为空。
var fooValue = (string)HttpContext.Current.Application["fooKey"];
显然,在我正在测试的方法中,该值也是空的。
到目前为止,我尝试的是嘲笑上下文 - 效果不佳。然后我在SO上看到了假的HTTP上下文解决方案,仍然不起作用。我什至尝试接受HttpApplicationStateBase
/HttpApplicationStateWrapper
并解决它们,但它变得一团糟,我觉得有比这更简单的解决方案。
我最终做的是使用这个惊人的库。对于那些想知道为什么什么都不起作用的人,这个 StackOverflow 问题解释了一个类似的问题(奇怪的是我在问之前找不到它)。
根据杰夫·斯特纳尔的回答:
HttpContext.Application 由您无法正常访问的私有单例提供。
您可以通过反射来设置它,但我个人甚至不会打扰。
相反,您应该将可测试代码与全局状态(如 HttpContext.Current)隔离开来:只需将您要查找的值传递到要测试的方法中即可。
你可以清楚地看到问题,看看 Reflector 中的代码(或者如果你已经下载了 .NET 源代码):每次调用 HttpContext.Application get 访问器时都会返回一个新的 HttpApplicationState 实例,除非某些东西(如 ASP.NET 框架)设置 HttpApplicationFactory._theApplicationFactory._state。
最终,我最终做了这样的事情:
using (HttpSimulator simulator = new HttpSimulator())
{
simulator.SimulateRequest(new Uri("http://localhost/"));
HttpContext.Current.Application.Add("fooKey", Foo);
this.httpContext = HttpContext.Current;
}
然后将保存的引用传递给我的方法。
更新 #1:虽然我的原始答案在编写使用HttpContext
某些方面的测试时可能很有用,但将其与 HttpContext.Application
属性一起使用并不容易,因为该属性依赖于 HttpApplicationFactory
类的static internal
属性。
可能的解决方案是不依赖于所测试方法中的 Application
属性,而是将值传递到方法中。例如:
cls.MethodUnderTest("foo value");
这意味着您可以更改测试类,使其不依赖于HttpContext.Application
(假设没有其他测试需要它),并且您的测试类将仅包含测试:
[TestMethod]
public void Test1()
{
// This would be the value you previously stored in the Application property
object fooValue = <some value>;
YourClassName cls = new YourClassName();
cls.MethodUnderTest(fooValue);
}
然后,如果您有其他测试使用不同的值测试该方法,则只需创建一个测试并传入该值,而不必先添加以将其添加到HttpContext.Application
。
作为旁注....在输入此内容时,我意识到我可能会对我的单元测试和 cookie 做同样的事情!
原始答案
我目前正在尝试类似的事情,但使用 cookie。我所做的是将HttpContext.Current
传递到类中,存储引用,然后在类方法中使用它。
所以:
public class MyClass
{
private HttpContext _httpContext;
// **** The caller would supply HttpContext.Current here ****
public MyClass(HttpContext httpContext) {
_httpContext = httpContext;
}
public void SomeMethod() {
...
// Do something here with _httpContext
...
}
}
然后,当涉及到单元测试时,我会做一些类似于您目前拥有的事情,如下所示:
public void Test1()
{
HttpRequest request = new HttpRequest(null, "http://tempuri.org", null);
HttpResponse response = new HttpResponse(null);
HttpContext httpContext = new HttpContext(request, response);
httpContext.Request.Cookies.Add(new HttpCookie("cookieName"));
MyClass cls = new MyClass(httpContext);
cls.SomeMethod();
}
根据您的情况,您可以更改引用HttpContext.Current
的类以接受HttpContext
实例并将HttpContext.Current
传递到其中,如上例所示。然后,无需在TestInitialize
中设置HttpContext.Current
,而是创建一个成员变量并使用该变量,而不是依赖于静态Current
属性。
例如:
private const string Foo = "foobar";
private HttpContext _httpContext = null;
[TestInitialize]
public void InitializeContext()
{
_httpContext = new HttpContext(new HttpRequest(
null, "http://tempuri.org", null), new HttpResponse(null));
_httpContext.Application["fooKey"] = Foo;
}
[TestMethod]
public void Test1() <-- **** Example Test Method ****
{
YourClassName cls = new YourClassName(_httpContext);
cls.MethodUnderTest();
}
[TestCleanup]
public void DisposeContext()
{
_httpContext = null;
}
我没有工作示例,所以我现在只是忘记了,但希望它能提供一些帮助。
为什么不尝试在接口中实现它并模拟该接口。然后,您可以使用依赖关系注入框架将其注入控制器
public interface IApplicationContext
{
string FooKey { get; set; }
}
public class ApplicationContext : IApplicationContext
{
public string FooKey
{
return HttpContext.Current.Application["fooKey"];
}
}
public class YourTests
{
[TestInitialize]
public void InitializeContext()
{
// From MOQ mocking framework. https://github.com/moq/moq4
Mock<IApplicationContext> applicationMock = new Mock<IApplicationContext>();
applicationMock.SetupGet(x => x.FooKey).Returns("foo");
}
}
public class YourController : Controller
{
private IApplicationContext applicationContext;
// Inject the application context using a dependency injection framework or manually in the constructor.
public YourController (IApplicationContext applicationContext)
{
this.applicationContext = applicationContext;
var foo = applicationContext.FooKey;
}
}