我正在尝试测试一个自定义的ActionResult,但我没有让它工作。 我正在写一个文件到响应流,所以我想在单元测试中做的是读取响应并验证它是否正确。
以下是我要测试的方法:
/// <summary>
/// Start writing the file.
/// </summary>
/// <param name="response">The response object.</param>
protected override void WriteFile(HttpResponseBase response)
{
// Convert the IList<T> to a datatable.
dataTable = list.ConvertToDatatable<T>();
// Add the header and the content type required for this view.
response.AddHeader("Content-Disposition", string.Format("attachment; filename={0}", filename));
response.ContentType = base.ContentType;
// Gets the current output stream.
var outputStream = response.OutputStream;
// Create a new memorystream.
using (var memoryStream = new MemoryStream())
{
WriteDataTable(memoryStream);
outputStream.Write(memoryStream.GetBuffer(), 0, (int)memoryStream.Length);
}
}
我已经在单元测试中尝试了以下内容:
HttpContextBaseMock = new Mock<HttpContextBase>();
HttpRequestMock = new Mock<HttpRequestBase>();
HttpResponseMock = new Mock<HttpResponseBase>();
HttpContextBaseMock.SetupGet(x => x.Request).Returns(HttpRequestMock.Object);
HttpContextBaseMock.SetupGet(x => x.Response).Returns(HttpResponseMock.Object);
var routes = new RouteCollection();
var controller = new CsvActionResultController();
controller.ControllerContext = new ControllerContext(HttpContextBaseMock.Object, new RouteData(), controller);
controller.Url = new UrlHelper(new RequestContext(HttpContextBaseMock.Object, new RouteData()), routes);
var result = controller.ExportToCSV();
但是,我没有让它发挥作用。
如果需要,这里是CsvActionResult的完整源代码(没有构造函数):
/// <summary>
/// Start writing the file.
/// </summary>
/// <param name="response">The response object.</param>
protected override void WriteFile(HttpResponseBase response)
{
// Add the header and the content type required for this view.
response.AddHeader("Content-Disposition", string.Format("attachment; filename={0}", filename));
response.ContentType = base.ContentType;
// Gets the current output stream.
var outputStream = response.OutputStream;
// Create a new memorystream.
using (var memoryStream = new MemoryStream())
{
WriteDataTable(memoryStream);
outputStream.Write(memoryStream.GetBuffer(), 0, (int)memoryStream.Length);
}
}
#endregion Methods
#region Helper Methods
/// <summary>
/// Writes a datatable to a given stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
private void WriteDataTable(Stream stream)
{
var streamWriter = new StreamWriter(stream, encoding);
// Write the header only if it's indicated to write.
if (includeRowHeader)
{ WriteHeaderLine(streamWriter); }
// Move to the next line.
streamWriter.WriteLine();
WriteDataLines(streamWriter);
streamWriter.Flush();
}
/// <summary>
/// Writes the header to a given stream.
/// </summary>
/// <param name="streamWriter">The stream to write to.</param>
private void WriteHeaderLine(StreamWriter streamWriter)
{
foreach (DataColumn dataColumn in dataTable.Columns)
{
WriteValue(streamWriter, dataColumn.ColumnName);
}
}
/// <summary>
/// Writes the data lines to a given stream.
/// </summary>
/// <param name="streamWriter"><The stream to write to./param>
private void WriteDataLines(StreamWriter streamWriter)
{
// Loop over all the rows.
foreach (DataRow dataRow in dataTable.Rows)
{
// Loop over all the colums and write the value.
foreach (DataColumn dataColumn in dataTable.Columns)
{ WriteValue(streamWriter, dataRow[dataColumn.ColumnName].ToString()); }
streamWriter.WriteLine();
}
}
/// <summary>
/// Write a specific value to a given stream.
/// </summary>
/// <param name="writer">The stream to write to.</param>
/// <param name="value">The value to write.</param>
private void WriteValue(StreamWriter writer, String value)
{
writer.Write(value);
writer.Write(delimeter);
}
有人能指出我正确的方向吗? 我是嘲笑的新手。
亲切的问候,
答案 0 :(得分:1)
就个人而言,我不会走这条路,以确保您的Web响应包含适当的结果,尤其是在单元测试场景中。如果您希望使用真正的Web交互进行全功能测试(即包含填充了正确值的所有相关属性),我将使用验收测试/ UI测试来确保您具有正确的文件导出行为。
但单元测试仍然很重要,因为您希望单独验证事物并确保快速反馈您实施的行为。编写这些Unit测试的方式略有不同,因为您知道在单元测试期间没有完整的ASP.NET运行时执行。
所以关于你的评论
例如:Response.OutputStream始终为null。 Response.ContentStream是awalys null。
请注意,只是对HttpContextBase,HttpResponseBase进行存根并将其分配给ControllerContext,并不意味着它将具有所有必需的属性,例如OutputStream,ContentStream,Response头,内容类型在Unit Testing执行上下文中设置。您正在处理由Moq.Mock提供的代理/模拟/存根对象(即HttpResponseMock),因此您无法像在正常执行ASP.NET Web期间那样获得可用的属性。这就是为什么在单元测试执行上下文中,您会看到这些属性返回null。
您可以使thes属性不返回null。您可以像对待其他虚拟属性一样存根这些属性。
httpResponseBaseStub.SetupGet(x => x.OutputStream).Returns(new Mock<Stream>().Object);
httpResponseBaseStub.SetupGet(x => x.ContentType).Returns("text/csv");
HttpResponseBase中的所有属性,方法都是虚拟的,因此您可以使用Moq.Mock向SUT提供所有这些属性的虚假表示。有一些先进的模拟技术,如AutoMocking,可以帮助你删除一些不必要的模拟,但默认情况下会将所有这些属性返回给你,但我不会详细介绍。
但是,这不是你所期望的,因为我上面描述的stubbinh / faking值并没有真正增加你在单元测试中需要验证的值。它们只是虚假的价值观。
那么下一个最好的方法是什么呢?
我认为通过验证使用期望值调用的一些关键HttpResponseBase属性来验证ExportToCSV的行为会更好。例如,在单元测试中,只需使用下面的预期值来验证调用的某些HttpResponse属性就足够了。
[TestMethod]
public void CsvActionResultController_ExportToCSV_VerifyResponsePropertiesAreSetWithExpectedValues()
{
var sut = new HomeController();
var httpResponseBaseMock = new Mock<HttpResponseBase>();
//This would return a fake Output stream to you SUT
httpResponseBaseMock.Setup(x => x.OutputStream).Returns(new Mock<Stream>().Object);
var httpContextBaseStub = new Mock<HttpContextBase>();
httpContextBaseStub.SetupGet(x => x.Response).Returns(httpResponseBaseMock.Object);
var controllerContextStub = new Mock<ControllerContext>();
controllerContextStub.SetupGet(x => x.HttpContext).Returns(httpContextBaseStub.Object);
sut.ControllerContext = controllerContextStub.Object;
var result = sut.Index();
httpResponseBaseMock.VerifySet(x => x.ContentType = "text/csv");
httpResponseBaseMock.Verify(x => x.AddHeader("Content-Disposition", "attachment; filename=somefile.csv"));
//Any other verifications...
}
另外here是另一个例子,但使用类似验证的方法略有不同。根据我的描述并通过测试ActionResult类型是一个FileContentResult,你可以提出一个相当不错的单元测试。
Assert.IsInstanceOfType(actual, typeof(FileContentResult));
更新(包含WriteFile方法)
protected void WriteFile(HttpResponseBase response)
{
// Add the header and the content type required for this view.
string format = string.Format("attachment; filename={0}", "somefile.csv");
response.AddHeader("Content-Disposition", format);
response.ContentType = "text/csv"; //if you use base.ContentType,
//please make sure this return the "text/csv" during test execution.
// Gets the current output stream.
var outputStream = response.OutputStream;
// Create a new memorystream.
using (var memoryStream = new MemoryStream())
{
// WriteDataTable(memoryStream);
outputStream.Write(memoryStream.GetBuffer(), 0, (int)memoryStream.Length);
}
}