如何在IHttpModules中测试HttpApplication事件

时间:2017-02-22 09:06:03

标签: c# unit-testing nunit moq httpmodule

我正在撰写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),而ATM是唯一的解决方案我找到is to create a wrapper这听起来并不像最直接的解决方案......

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

如何测试一个事件IHttpModules?有没有办法模仿HttpApplication?优先使用Moq

编辑:以下是我要测试的代码...它从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);
        }
    }
}

1 个答案:

答案 0 :(得分:2)

以下显示了使上述案例可以进行测试的潜在工作

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派生类中,该类允许将模拟交换到其位置进行测试。

现在应该允许将TDD应用于模块。