让我们看看中的以下语句
Module.ApplicationEndRequest()
方法:
context.Response.Write(builder.ToString());
当从单元测试执行此代码时,
context.Response
是您在MoqHttpContext中设置的模拟。CreateBaseMocks():
MockResponse = new Mock<HttpResponseBase>();
// ...
MockContext.Setup(ctx => ctx.Response).Returns(MockResponse.Object);
你不能指望你打电话给
Write()
方法,然后可以读回相同的数据。Mock是一个伪对象。其默认实现
写入()
方法不执行任何操作,传递的数据将丢失。
要解决此问题,可以在上设置回调
Response
mock,将传递的数据写入流,然后在读取时返回。实际上你离它很近。
在里面
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()
,以便您写入的html数据
UnitTest1.TestMethod1()
追加,而不是覆盖:
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模块附加的数据就在那里。
更新
(修复过滤器问题)
当前代码有3个不同的问题会妨碍UT过滤器的正确工作。
-
你尝试了几个嘲弄的选项
Filter
然而,它们似乎都不正确。使用Moq模拟属性getter的正确方法是:
MockResponse.SetupGet(response => response.Filter).Returns(filter);
删除所有其他模拟语句
response.Filter
,但暂时不要添加上述声明,这还不是最终版本。
-
您有以下签入
单元ApplicationEndRequest()
方法:
if (context.Response.Filter is ResponseSniffer filter)
{
// ...
执行UT时,
context.Response.Filter
是一个
MemoryStream
不是一个
ResponseSniffer
. 调用的Setter
Module
建造商:
context.Response.Filter = new ResponseSniffer(context.Response.Filter);
实际上不会影响返回的值
滤器
getter,因为它是一个模拟,当前总是返回
内存流
安装时使用的
SetupGet
. 要解决此问题,您应该实际模拟属性行为:保存传递给setter的值并在getter中返回它。以下是的最终设置
回答滤器
属性:
Stream filter = new MemoryStream();
MockResponse.SetupSet(response => response.Filter = It.IsAny<Stream>()).Callback<Stream>(value => filter = value);
MockResponse.SetupGet(response => response.Filter).Returns(() => filter);
确保已删除的所有其他模拟
回答滤器
所有物
-
您应该解决的最后一个问题是
单元
来自UT的调用。目前的顺序如下:
httpContext.StreamWrite(httpContext.Response.Filter, html);
// ...
var module = new Module();
module.PreRequestHandlerExecute(mockHttpContext.HttpContext());
但是
PreRequestHandlerExecute
集合
Response.Filter
以实例
响应Sniffer
. 那么什么时候
httpContext.StreamWrite
上面叫做,
httpContext.Response.Filter
实际持有的实例
内存流
不
响应Sniffer
. 因此,您应该做的最后一个修复是更改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应该可以工作。然而,当前的测试非常繁琐。事实证明,要花这么多时间才能理解为什么它不起作用。这样的测试很难维护和修复,随着时间的推移,它们会成为一种真正的痛苦。
此外,它更像是集成测试而不是单元测试,因为它调用了几个具有不同功能的类-
响应Sniffer
和
单元
.
您应该强烈考虑重新设计当前测试。好的开始是对
响应Sniffer
和
单元
课程。
最有价值的测试
响应Sniffer
用于验证写入数据是否已在中注册
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);
}
}
关于
单元
类中,应进行以下几项检查:
-
PreRequestHandlerExecute()
集合
回答滤器
具有的实例
响应Sniffer
.
-
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"));
}
}
正如您所看到的,测试非常简单和直接。你不需要这些
MoqHttpContext
和
MoqHttpContextExtensions
还有很多嘲笑和帮助。另一个好处是,如果某些测试被破坏,则更容易确定根本原因并加以修复。
如果您是单元测试新手,并且正在寻找关于它的良好信息源,我强烈建议您阅读这本书
The Art of Unit Testing
罗伊·奥舍洛夫。