如何在 IHttpModules 中测试 HttpApplication 事件

How to test HttpApplication events in IHttpModules

我正在写 HttpModule 并且需要测试它,我正在使用 C#.NET4.5.2NUnitMoq

我要测试的方法是Context_BeginRequest:

public class XForwardedForRewriter : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.BeginRequest += Context_BeginRequest;
    }

    public void Context_BeginRequest(object sender, EventArgs e) { ... }
}

sender 这里是 HttpApplication,这就是问题开始的地方,...可以创建 HttpApplication 的实例,但是无法设置 HttpContext因为它是只读的并且无法将其传入(通过构造函数或类似的东西)...

我没有 VS2015 Ultimate 并且无法使用 Microsoft.FakesShims), and ATM the only solution for this I have found is to create a wrapper 这听起来不像是最直接的解决方案....

当我想到这一点时,我确信有人已经 运行 解决了这个确切的问题(因为每次在 TDD 中编写 HttpModule 他都需要模拟 HttpApplication或者做一些解决方法)

如何进行一项测试IHttpModules?有没有办法模拟 HttpApplication? 最好用 Moq.

编辑: 这是我要测试的代码...它是 header re-writer 从 PROXY v2 二进制到旧的X-Forwarded-For...

public class XForwardedForRewriter : IHttpModule
{
    public void Dispose()
    {
        throw new NotImplementedException();
    }

    byte[] proxyv2HeaderStartRequence = new byte[12] { 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A };

    public void Init(HttpApplication context)
    {
        context.BeginRequest += Context_BeginRequest;
    }

    public void Context_BeginRequest(object sender, EventArgs e)
    {
        var request = ((HttpApplication)sender).Context.Request;

        var proxyv2header = request.BinaryRead(12);
        if (!proxyv2header.SequenceEqual(proxyv2HeaderStartRequence))
        {
            request.Abort();
        }
        else
        {
            var proxyv2IpvType = request.BinaryRead(5).Skip(1).Take(1).Single();
            var isIpv4 = new byte[] { 0x11, 0x12 }.Contains(proxyv2IpvType);
            var ipInBinary = isIpv4 ? request.BinaryRead(12) : request.BinaryRead(36);
            var ip = Convert.ToString(ipInBinary);

            var headers = request.Headers;
            Type hdr = headers.GetType();
            PropertyInfo ro = hdr.GetProperty("IsReadOnly",
                BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy);

            ro.SetValue(headers, false, null);

            hdr.InvokeMember("InvalidateCachedArrays",
                BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,
                null, headers, null);

            hdr.InvokeMember("BaseAdd",
                BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,
                null, headers,
                new object[] { "X-Forwarded-For", new ArrayList { ip } });

            ro.SetValue(headers, true, null);
        }
    }
}

以下显示了使上述案例可测试的潜在解决方法

public class XForwardedForRewriter : IHttpModule {
    public void Dispose() {
        throw new NotImplementedException();
    }

    byte[] proxyv2HeaderStartRequence = new byte[12] { 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A };

    public void Init(HttpApplication context) {
        context.BeginRequest += Context_BeginRequest;
    }

    public Func<object, HttpRequestBase> GetRequest = (object sender) => {
        return new HttpRequestWrapper(((HttpApplication)sender).Context.Request);
    };

    public void Context_BeginRequest(object sender, EventArgs e) {
        var request = GetRequest(sender);

        var proxyv2header = request.BinaryRead(12);
        if (!proxyv2header.SequenceEqual(proxyv2HeaderStartRequence)) {
            request.Abort();
        } else {
            var proxyv2IpvType = request.BinaryRead(5).Skip(1).Take(1).Single();
            var isIpv4 = new byte[] { 0x11, 0x12 }.Contains(proxyv2IpvType);
            var ipInBinary = isIpv4 ? request.BinaryRead(12) : request.BinaryRead(36);
            var ip = Convert.ToString(ipInBinary);

            var headers = request.Headers;
            var hdr = headers.GetType();
            var ro = hdr.GetProperty("IsReadOnly",
                BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy);

            ro.SetValue(headers, false, null);

            hdr.InvokeMember("InvalidateCachedArrays",
                BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,
                null, headers, null);

            hdr.InvokeMember("BaseAdd",
                BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,
                null, headers,
                new object[] { "X-Forwarded-For", new ArrayList { ip } });

            ro.SetValue(headers, true, null);
        }
    }
}

测试最终会像

[TestClass]
public class XForwardedForRewriterTests {

    [TestMethod]
    public void Request_Should_Abort() {
        //Arrange
        var request = Mock.Of<HttpRequestBase>();

        var sut = new XForwardedForRewriter();
        //replace with mock request for test
        sut.GetRequest = (object sender) => request;

        //Act
        sut.Context_BeginRequest(new object(), EventArgs.Empty);

        //Assert
        var mockRequest = Mock.Get(request);
        mockRequest.Verify(m => m.Abort(), Times.AtLeastOnce);
    }


    [TestMethod]
    public void Request_Should_Forward() {
        //Arrange
        var request = Mock.Of<HttpRequestBase>();

        var mockRequest = Mock.Get(request);
        //setup mocked request with desired behavior for test
        var proxyv2HeaderStartRequence = new byte[12] { 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A };
        mockRequest
            .Setup(m => m.BinaryRead(12))
            .Returns(proxyv2HeaderStartRequence);

        var fakeProxyv2IpvType = new byte[5] { 0x00, 0x12, 0x00, 0x00, 0x00 };
        mockRequest
            .Setup(m => m.BinaryRead(5))
            .Returns(fakeProxyv2IpvType);

        var headers = new NameValueCollection();
        mockRequest.Setup(m => m.Headers).Returns(headers);

        var sut = new XForwardedForRewriter();
        //replace with mock request for test
        sut.GetRequest = (object sender) => request;

        //Act
        sut.Context_BeginRequest(new object(), EventArgs.Empty);

        //Assert
        //...check request headers
        var xForwardedFor = headers["X-Forwarded-For"];
        Assert.IsNotNull(xForwardedFor);
    }

}

对 Sut 的一个观察是 ip 解析为 "System.Byte[]",我认为这不是预期的行为。重新检查 proxyv2HeaderStartRequence.

除了添加Factory方法访问请求外,其余被测代码保持不变。观察实际的实现,请求是如何包装在 HttpRequestBase 派生的 class 中的,这允许在其位置交换模拟以进行测试。

这现在应该允许在模块中应用 TDD。