【问题标题】:How to unit test this method in c#?如何在 C# 中对该方法进行单元测试?
【发布时间】:2013-01-16 13:10:47
【问题描述】:

我正在学习单元测试。如何使用 nunit 和 rhino mock 对该方法进行单元测试?

public ActionResult PrintCSV(Byte[] bytes, string fileName)
{
    var file = File(bytes, "application/vnd.ms-excel");
    var cd = new System.Net.Mime.ContentDisposition()
    {
        CreationDate = DateTime.Now,
        FileName = fileName,
        Inline = false
    };
    Response.AppendHeader("Content-Disposition", cd.ToString());
    return file;
}

【问题讨论】:

  • 首先你需要决定你到底想测试什么?
  • 在 Assert 中应该检查,预期文件 = 实际文件
  • 那么你的第一个测试大概应该是结果实际上是一个文件?然后你可以扩展它并确保标题是正确的。
  • 我可以补充一下,编写第一个测试应该让您意识到您可以使用 FileResult 而不是 ActionResult 以使方法的意图更清晰。这是 TDD 的一大优点,您可以看到潜在的改进。

标签: c# asp.net-mvc-3 nunit


【解决方案1】:

您需要模拟 HttpContext。这是一个示例(它是 MSTest,但我想移植到 NUnit 不会很痛苦 - 您只需要重命名几个属性):

[TestMethod]
public void PrintCSV_Should_Stream_The_Bytes_Argument_For_Download()
{
    // arrange 
    var sut = new HomeController();
    var bytes = new byte[] { 1, 2, 3 };
    var fileName = "foobar";
    var httpContext = MockRepository.GenerateMock<HttpContextBase>();
    var response = MockRepository.GenerateMock<HttpResponseBase>();
    httpContext.Expect(x => x.Response).Return(response);
    var requestContext = new RequestContext(httpContext, new RouteData());
    sut.ControllerContext = new ControllerContext(requestContext, sut);

    // act
    var actual = sut.PrintCSV(bytes, fileName);

    // assert
    Assert.IsInstanceOfType(actual, typeof(FileContentResult));
    var file = (FileContentResult)actual;
    Assert.AreEqual(bytes, file.FileContents);
    Assert.AreEqual("application/vnd.ms-excel", file.ContentType);
    response.AssertWasCalled(
        x => x.AppendHeader(
            Arg<string>.Is.Equal("Content-Disposition"),
            Arg<string>.Matches(cd => cd.Contains("attachment;") && cd.Contains("filename=" + fileName))
        )
    );
}

如您所见,这里有一些管道代码来设置测试。我个人使用MvcContrib.TestHelper,因为它简化了很多管道代码并使测试更具可读性。看看这个:

[TestClass]
public class HomeControllerTests : TestControllerBuilder
{
    private HomeController sut;

    [TestInitialize]
    public void TestInitialize()
    {
        this.sut = new HomeController();
        this.InitializeController(this.sut);
    }

    [TestMethod]
    public void PrintCSV_Should_Stream_The_Bytes_Argument_For_Download()
    {
        // arrange 
        var bytes = new byte[] { 1, 2, 3 };
        var fileName = "foobar";

        // act
        var actual = sut.PrintCSV(bytes, fileName);

        // assert
        var file = actual.AssertResultIs<FileContentResult>();
        Assert.AreEqual(bytes, file.FileContents);
        Assert.AreEqual("application/vnd.ms-excel", file.ContentType);
        this.HttpContext.Response.AssertWasCalled(
            x => x.AppendHeader(
                Arg<string>.Is.Equal("Content-Disposition"),
                Arg<string>.Matches(cd => cd.Contains("attachment;") && cd.Contains("filename=" + fileName))
            )
        );
    }
}

现在测试更加清晰,我们可以立即看到初始化阶段、被测方法的实际调用和断言。

备注:话虽这么说,但我不太明白控制器动作的意义在于,控制器动作将字节数组作为参数,只是为了将其流回客户端。我的意思是为了调用它,客户端需要已经拥有该文件,那么有什么意义呢?但我想这只是为了说明目的。在您的实际方法中,字节数组不作为参数传递,而是在控制器操作中从某些外部依赖项中检索。在这种情况下,您也可以模拟这种依赖关系(当然,假设您已经正确构建了您的层并且它们足够弱耦合)。

【讨论】:

  • 很高兴我能帮上忙。您还有什么想问的或者您可以考虑接受这个答案吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-29
  • 1970-01-01
  • 2010-11-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多