繁体   English   中英

单元测试一个ActionResult

[英]Unit test an ActionResult

我正在尝试测试自定义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);
    }

有人可以指出我正确的方向吗? 我是新来的。

亲切的问候,

我个人不会特别确保在单元测试中您的网络响应包含适当的结果。 如果您希望通过真正的Web交互进行功能全面的测试(即包含填充有正确值的所有相关属性),我将使用验收测试/ UI测试来确保文件导出的行为正确。

但是 ,单元测试仍然很重要,因为您要孤立地验证事物并确保快速获得有关所实现行为的反馈。 知道您在单元测试期间没有完整的ASP.NET运行时执行,编写这些单元测试的方式略有不同。

所以关于你的评论

例如:Response.OutputStream始终为null。 Response.ContentStream为空。

请注意,仅存根HttpContextBase,HttpResponseBase并将其分配给ControllerContext,并不意味着它将具有所有必需的属性,例如OutputStream,ContentStream,Response头以及在单元测试执行上下文中设置的内容类型。 您正在处理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 / fake值并没有真正为您需要在单元测试中验证的值增加价值。 它们只是假值。

那么,下一个最佳方法是什么?

我认为最好通过验证一些用预期值调用的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...
    }

也是另一个示例,但是使用类似验证的方法略有不同。 根据我的描述,通过测试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);
        }
    }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM