How to test HttpApplication events in IHttpModules

拥有回忆 提交于 2019-12-24 03:27:59

问题


I am writing HttpModule and need to test it, I am using C#, .NET4.5.2, NUnit and Moq.

Method I am trying to test is Context_BeginRequest:

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

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

sender here is HttpApplication and this is where the problems start,... one can create instance of HttpApplication however there is no way to set HttpContext since it is read only and there is no way to pass it in (via constructor or something alike)...

I don't have VS2015 Ultimate and can't use Microsoft.Fakes (Shims), and ATM the only solution for this I have found is to create a wrapper which doesn't sound like most straightforward solution....

When I think about this I am sure that someone has already ran into this exact problem (as every time one is writing HttpModule in TDD he will need to mock HttpApplication or do some workaround)

How does one test events IHttpModules? Is there a way of Mocking HttpApplication? preferebly with Moq.

EDIT: Here is the code I am trying to test... it's header re-writer from PROXY v2 binary to good old 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:


The following shows a potential work around for making the above case test-able

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);
        }
    }
}

The tests would end up like

[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);
    }

}

One observation of the Sut is that the ip resolves to "System.Byte[]" which I believe is not expected behavior. Recheck the proxyv2HeaderStartRequence.

Apart from adding the Factory method to access the request, the rest of the code under test remained the same. Observe for the actual implementation, how the request was wrapped in a HttpRequestBase derived class which allowed for a mock to be swapped in its place for testing.

This should now allow for the application of TDD with the module.



来源:https://stackoverflow.com/questions/42387013/how-to-test-httpapplication-events-in-ihttpmodules

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!