问题
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