【问题标题】:How to test HttpApplication events in IHttpModules如何在 IHttpModules 中测试 HttpApplication 事件
【发布时间】:2019-01-15 16:04:34
【问题描述】:

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

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

如何测试事件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);
        }
    }
}

【问题讨论】:

  • 你到底想测试什么?显示 SUT,也许可以找到解决方法。

标签: c# unit-testing nunit moq httpmodule


【解决方案1】:

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

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。

【讨论】:

  • 您好 Nkosi,这确实有效,我必须说非常聪明。谢谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-01
  • 1970-01-01
  • 2020-12-11
  • 1970-01-01
  • 2014-02-12
  • 2014-09-30
相关资源
最近更新 更多