如何测试IHttpModules中的HttpApplication事件

问题描述:

我在写HttpModule并需要测试它,我使用C#,.NET4.5.2,NUnitMoq如何测试IHttpModules中的HttpApplication事件

方法,我想测试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或做一些解决方法)

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

你到底想要测试什么?显示SUT,也许可以找到解决办法。 – Nkosi

下面显示了围绕一个潜在的工作使上述情况下的测试,能够在SUT的

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

} 

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

一个观察是ip解析为"System.Byte[]"我相信这是不期望的行为。重新检查proxyv2HeaderStartRequence

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

现在应该允许TDD与模块一起使用。

+1

嗨Nkosi,这确实有用,我必须说很聪明。谢谢 –