在单元测试中设置HttpContext.Current.Application键

5

我有一个方法,在内部使用静态的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 并通过它们工作,但结果变得混乱,并且我感觉有比那更简单的解决方案。

3个回答

3
我最终使用了这个神奇的库:这里。对于那些想知道为什么什么都不起作用的人,这个 StackOverflow 问题解释了一个类似的问题(很奇怪我在提问之前找不到它)。
根据Jeff Sternal的回答:
HttpContext.Application由一个您通常无法访问的私有单例提供。 您可以通过反射设置它,但我个人甚至不会费心去做这件事。 相反,您应该将可测试的代码与HttpContext.Current等全局状态隔离开:只需将您要测试的值传递到方法中即可。
您可以通过反编译器(或者如果您已经下载了.NET源代码,则可以在其中查看)清楚地看到问题所在:除非某些东西(例如ASP.NET框架)设置了HttpApplicationFactory._theApplicationFactory._state,否则HttpContext.Application获取访问器每次都会返回一个新的HttpApplicationState实例。
最终,我做了这样的事情:
using (HttpSimulator simulator = new HttpSimulator())
{
    simulator.SimulateRequest(new Uri("http://localhost/"));
    HttpContext.Current.Application.Add("fooKey", Foo);
    this.httpContext = HttpContext.Current;
}

然后将保存的引用传递给我的方法。

1

更新 #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中。
顺便说一下... ...在打这段话的时候,我已经意识到我可能可以用我的单元测试和cookies做同样的事情!
原始答案:
我正在尝试类似的事情,但是使用cookies。 我是将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;
}

我手头没有一个可用的示例,所以目前只能凭记忆来解释,但希望它能提供一些帮助。


1
我尝试了完全相同的方法。仍然不起作用。我感觉应用程序属性依赖于其他东西并且在单元测试中没有设置。密钥被添加了,没有任何异常,但在MyClass中检索它会导致null - arnaudoff

0
为什么不尝试在接口中实现并模拟该接口。然后,您可以使用依赖注入框架将其注入到您的控制器中。
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;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接