gpt4 book ai didi

c# - 使用Response.Filter和Response.Write测试Http模块

转载 作者:太空宇宙 更新时间:2023-11-03 20:57:34 26 4
gpt4 key购买 nike

我正在使用一个Http模块,该模块仅记录响应时间和大小,然后将结果附加到响应正文中。

我的模块看起来像这样:

public override void PreRequestHandlerExecute(HttpContextBase context)
{
// Add a filter to capture response stream
context.Response.Filter = new ResponseSniffer(context.Response.Filter);
}

public override void ApplicationEndRequest(HttpContextBase context)
{
....
context.Response.Write(builder.ToString());
}


我现在希望对该模块进行单元测试。我是单元测试的新手。我已经修改了 o2platform中的代码以获得了最低限度的httpcontext,到目前为止,它仍然可以正常工作。但是,响应过滤器似乎确实在Pre中设置了,响应主体是我从测试设置中初始化的内容。

我尝试了一些选项(并阅读了很多东西),但是这些似乎都不起作用:

public Mock<HttpResponseBase> MockResponse { get; set; }
...
var outputStream = new MemoryStream();
var filter = new MemoryStream();

//MockResponse.Setup(response => response.OutputStream).Returns(GetMockStream(outputStream).Object);
//MockResponse.Setup(response => response.Filter).Returns(GetMockStream(filter).Object);

MockResponse.Setup(response => response.OutputStream).Returns(() => outputStream);
//MockResponse.SetupSet(response => response.OutputStream = It.IsAny<Stream>()).Returns(() => outputStream);

MockResponse.Setup(response => response.Filter).Returns(() => filter);
MockResponse.SetupSet(response => response.Filter = It.IsAny<Stream>());
MockResponse.SetupSet(response => response.Filter = It.IsAny<ResponseSniffer>());


测试方法

[TestMethod]
public void TestMethod1()
{
var mockHttpContext = new MoqHttpContext();

var httpContext = mockHttpContext.HttpContext();
var html = @"<html>
<head></head>
<body>
<h1>Hello World</h1>
</body>
</html>";

httpContext.ResponseWrite(html);
httpContext.StreamWrite(httpContext.Response.Filter, html);


var module = new Module();

module.PreRequestHandlerExecute(mockHttpContext.HttpContext());
module.ApplicationBeginRequest(mockHttpContext.HttpContext());
module.ApplicationEndRequest(mockHttpContext.HttpContext());

var responseRead = httpContext.ResponseRead(); //extension method to get output stream
var b = 1; //put breakpoint here
}


我意识到测试需要断言而不是断点。我也意识到测试应该被分解。

代码仓库

Github

最佳答案

让我们看一下Module.ApplicationEndRequest()方法中的以下语句:

context.Response.Write(builder.ToString());


当从单元测试执行此代码时, context.Response是您在MoqHttpContext.CreateBaseMocks()中设置的模拟:

MockResponse = new Mock<HttpResponseBase>();
// ...
MockContext.Setup(ctx => ctx.Response).Returns(MockResponse.Object);


您不能期望您在模拟程序上调用 Write()方法,然后可以读回相同的数据。模拟是伪造的对象。它的 Write()方法的默认实现不执行任何操作,并且传递的数据只会丢失。

要解决此问题,您可以在 Response模拟上设置一个回调,该回调将传递的数据写入流,然后在读取时将其返回。您实际上非常接近它。

MoqHttpContext类中声明一个流,您将在其中保存数据:

public class MoqHttpContext
{
private readonly MemoryStream _outputStream = new MemoryStream();

// ...
}


然后在 CreateBaseMocks()方法中设置回调:

public MoqHttpContext CreateBaseMocks()
{
// ...

MockResponse = new Mock<HttpResponseBase>();
MockResponse.Setup(x => x.Write(It.IsAny<string>())).Callback<string>(s =>
{
var data = Encoding.ASCII.GetBytes(s);
_outputStream.Write(data, 0, data.Length);
_outputStream.Flush();
_outputStream.Position = 0;
});

// ...
}


您还应该删除在 inputStream中将 0位置设置为 MoqHttpContextExtensions.StreamWrite()的行,以便附加在 UnitTest1.TestMethod1()中写入的html数据,而不是被覆盖:

public static HttpContextBase StreamWrite(this HttpContextBase httpContextBase, Stream inputStream, string text)
{
if (inputStream == null) inputStream = new MemoryStream();
var streamWriter = new StreamWriter(inputStream);

inputStream.Position = inputStream.Length;
streamWriter.Write(text);
streamWriter.Flush();
// Remove this line
//inputStream.Position = 0;

return httpContextBase;
}


而已。现在,如果您在测试中检查 responseRead的值,您将看到Http模块附加的数据在那里。

UPDATE(修复过滤器问题)

当前代码存在3个不同的问题,这些问题阻止了UT滤波器的正确工作。


您尝试了一些模拟 Filter属性的选项,但是似乎都不正确。用Moq模拟属性获取器的正确方法是:

MockResponse.SetupGet(response => response.Filter).Returns(filter);


删除所有其他用于模拟 response.Filter的语句,但不要添加上述语句,它不是最终版本。
您可以在 Module.ApplicationEndRequest()方法中进行以下检查:

if (context.Response.Filter is ResponseSniffer filter)
{
// ...


执行UT时, context.Response.FilterMemoryStream而不是 ResponseSniffer。在 Module构造函数中调用的setter:

context.Response.Filter = new ResponseSniffer(context.Response.Filter);


实际上不会影响 Filter getter返回的值,因为它是一个模拟,当前总是返回您使用 MemoryStream设置的 SetupGet实例。要解决此问题,您实际上应该模拟属性行为:保存传递给setter的值,并将其返回到getter中。这是 response.Filter属性的最终设置:

Stream filter = new MemoryStream();
MockResponse.SetupSet(response => response.Filter = It.IsAny<Stream>()).Callback<Stream>(value => filter = value);
MockResponse.SetupGet(response => response.Filter).Returns(() => filter);


确保已删除 response.Filter属性的所有其他模拟。
您应该解决的最后一个问题是UT中 Module调用的顺序。当前顺序如下:

httpContext.StreamWrite(httpContext.Response.Filter, html);
// ...
var module = new Module();
module.PreRequestHandlerExecute(mockHttpContext.HttpContext());


但是 PreRequestHandlerExecuteResponse.Filter的实例设置 ResponseSniffer。因此,当上面的 httpContext.StreamWrite被调用时, httpContext.Response.Filter实际上保存着 MemoryStream的实例,而不是 ResponseSniffer。因此,您应该做的最后一个修复是更改UT主体中语句的顺序:

// ...

var module = new Module();
module.PreRequestHandlerExecute(mockHttpContext.HttpContext());

httpContext.ResponseWrite(html);
httpContext.StreamWrite(httpContext.Response.Filter, html);

module.ApplicationBeginRequest(mockHttpContext.HttpContext());
module.ApplicationEndRequest(mockHttpContext.HttpContext());

// ...



更新(UT重新设计)

此时,您的UT应该可以工作了。但是,当前的测试非常麻烦。花费大量时间来了解为什么它不起作用的事实证明了这一点。这样的测试很难维护和修复,随着时间的推移,它们变成了真正的痛苦。

此外,它是集成测试而不是单元测试,因为它调用具有不同功能的几个类- ResponseSnifferModule

您应该强烈考虑重新设计当前测试。好的开始是分别对 ResponseSnifferModule类进行测试。

ResponseSniffer的最有价值的测试是验证书面数据是否已注册在 RecordStream中的测试:

[TestClass]
public class ResponseSnifferTests
{
[TestMethod]
public void Write_WritesDataToRecordStream()
{
// Arrange

var inData = new byte[] { 0x01 };

var target = new ResponseSniffer(Mock.Of<Stream>());

// Act

target.Write(inData, 0, inData.Length);

// Assert

target.RecordStream.Position = 0;
var outData = new byte[inData.Length];
int outSize = target.RecordStream.Read(outData, 0, outData.Length);
Assert.AreEqual(inData.Length, outSize);
CollectionAssert.AreEqual(inData, outData);
}
}


关于 Module类,应进行以下检查:


PreRequestHandlerExecute()Response.Filter的实例设置 ResponseSniffer
ApplicationBeginRequest()Stopwatch添加到 context.Items词典。
ApplicationEndRequest()将请求信息写入响应。


UT方法意味着在单独的测试中检查这些事实。以下是这3种测试的示例:

[TestClass]
public class ModuleTests
{
[TestMethod]
public void PreRequestHandlerExecuteShouldSetResponseSnifferAsFilter()
{
// Arrange

Stream filter = null;
Mock<HttpResponseBase> httpResponseMock = new Mock<HttpResponseBase>();
httpResponseMock.SetupSet(response => response.Filter = It.IsAny<Stream>()).Callback<Stream>(value => filter = value);

Mock<HttpContextBase> httpContextStub = new Mock<HttpContextBase>();
httpContextStub.SetupGet(x => x.Response).Returns(httpResponseMock.Object);

var target = new Module();

// Act

target.PreRequestHandlerExecute(httpContextStub.Object);

// Assert

Assert.IsNotNull(filter);
Assert.IsInstanceOfType(filter, typeof(ResponseSniffer));
}

[TestMethod]
public void ApplicationBeginRequestShouldStoreStopwatchInContextItems()
{
// Arrange

var items = new Dictionary<string, object>();

Mock<HttpContextBase> httpContextStub = new Mock<HttpContextBase>();
httpContextStub.SetupGet(x => x.Items).Returns(items);

var target = new Module();

// Act

target.ApplicationBeginRequest(httpContextStub.Object);

// Assert

Assert.IsTrue(items.ContainsKey("X-ResponseTime"));
Assert.IsInstanceOfType(items["X-ResponseTime"], typeof(Stopwatch));
}

[TestMethod]
public void ApplicationEndRequestShouldAddRequestInfoToResponse()
{
// Arrange

Mock<HttpRequestBase> httpRequestMock = new Mock<HttpRequestBase>();
httpRequestMock.SetupGet(x => x.FilePath).Returns("/test");

string writtenData = null;
Mock<HttpResponseBase> httpResponseMock = new Mock<HttpResponseBase>();
httpResponseMock.Setup(x => x.Write(It.IsAny<string>())).Callback<string>(s => writtenData = s);

Mock<HttpContextBase> httpContextStub = new Mock<HttpContextBase>();
httpContextStub.SetupGet(x => x.Request).Returns(httpRequestMock.Object);
httpContextStub.SetupGet(x => x.Response).Returns(httpResponseMock.Object);
httpContextStub.SetupGet(x => x.Items).Returns(new Dictionary<string, object> { ["X-ResponseTime"] = new Stopwatch() });

var target = new Module();

// Act

target.ApplicationEndRequest(httpContextStub.Object);

// Assert

Assert.IsTrue(Regex.IsMatch(writtenData, @"Response Size: \d+ bytes<br/>"));
Assert.IsTrue(Regex.IsMatch(writtenData, @"Module request time: \d+ ms"));
}
}


如您所见,测试非常简单明了。您不再需要带有大量模拟和帮助程序的 MoqHttpContextMoqHttpContextExtensions。另一个好处-如果某些测试失败了,则更容易找出根本原因并加以解决。

如果您是单元测试的新手,并且正在寻找有关它的良好信息来源,我强烈建议Roy Osherove撰写的书 The Art of Unit Testing

关于c# - 使用Response.Filter和Response.Write测试Http模块,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48888130/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com