设置模拟到单元测试Web API Post时遇到困难

时间:2013-05-30 17:55:48

标签: c# asp.net-mvc unit-testing asp.net-web-api moq

我正在尝试使用MSTest和Moq设置一个单元测试,用于将json从表单发布到数据库的实时系统。系统本身工作正常,但我的任务是尝试为它构建一些测试。我正在使用的视图中的ajax调用将跟随HttpPost方法之一的控制器:

[HttpPost]
public ActionResult Add(Request model)
{
    return ProcessRequest(model, UserAction.Create);
}

这导致了WebAPI控制器:

public int Post([FromBody]Request value)
    {
        try
        {
            var id = myRepository.AddRequest(value);

            foreach (var day in value.Days)
            {
                day.RequestId = id;
                myRepository.AddRequestDay(day);
            }

            return id;
        }
        catch
        {
            return -1;
        }
    }

通过我的测试,我认为使用TransactionScope是个好主意,所以我实际上并没有在数据库中保存任何数据。如果有更好的方法,请赐教:

[TestMethod]
public void API_Request_Post()
{
    using (TransactionScope ts = new TransactionScope())
    {
        var jsonObject = //some json scraped from a test post
        var request = new Mock<HttpRequestBase>();
        //This is where I'm stuck. I can't find anything in Setup that lets me prep the Post body for when the controller gets to it.
        //request.Setup(x => x.InputStream).Returns(jsonObject);
        RequestController controller = new RequestController();
        //This is another point that I don't understand. I make the call for post happy with a reference to the model instead of the actual json?
        var result = controller.Post(new Models.Request() );
        Assert.IsTrue(result > -1);
    }
}

任何帮助试图确定我需要给我的json的HttpRequest的哪个部分都会非常感激(并且帮助我理解Post会只是锦上添花)。

1 个答案:

答案 0 :(得分:3)

除了

我希望我不会告诉你一些你已经知道的事情,但看起来你可能在质疑从哪里开始?这是测试中最难的部分......

为了确保我们在同一页面上,了解测试内容的关键是描述场景,以及单元测试该场景的关键是隔离。

这意味着你想要分离关于“被测试”的课程。

此外,如果您先编写测试,然后再编写代码来进行测试,则代码更容易测试。你不是那种情况,所以这意味着你拥有的可能不的代码可以在不改变它的情况下进行测试。

最后,鉴于任何外部/第三方系统,除非您正在进行“探索性测试”,否则您不想测试第三方的内容,即http发布/获取。相反,您希望测试代码及其执行方式。

假设你知道这一切或那一切都有意义,那么,这一部分也将是显而易见的。

Moq或任何其他模拟框架,旨在代表您所测试的类与之协作的对象/服务,以帮助隔离。给定两个类ClassA和ClassB,其中ClassA作用于ClassB,您希望在将它提供给ClassA时伪造/模拟B类,这样您就可以断言/验证ClassA的行为与给定方案的预期相同。这一开始看起来很幼稚,但考虑到你也会对ClassB做同样的事情,然后你会有一套与他们测试的内容相关的测试,这会给你很大的覆盖率。

隔离的关键是注入,确保如果ClassA作用于ClassB,你将ClassB传递给ClassA的构造函数,这样你就可以给它一个假的类B.除了响应,B类不应该做任何事情你怎么说它应该回应所以你可以证明ClassA在那种情况下表现得恰到好处。

有些人不屑于改变代码以使其可测试,但我的论点是,如果你编写的代码首先是可测试的,你就不必改变它,所以尝试重构而不是重新设计。

测试什么

所以,这意味着你需要一些不同的场景,每个场景都有一个与你关心的内容相关的测试。

良好测试的关键是弄清楚你想要测试的是什么,然后安排你的测试,以便明确你在做什么。

  • 测试类名称不需要“测试”;这是多余的。解释一下这个场景是什么;谁参与,等等。

  • 测试方法应该说明您关心测试的操作是什么;你在哪个州,等等。

  • **在方法**中现在按照“安排,行动,断言” (又名给定,当时,然后)方法:

  • 安排:设置您需要的所有模拟或任何变量,包括您正在测试的一个类,例如您的真实控制器,但假冒myRepository和假{{1} }

  • 行动执行实际操作,例如value

  • 断言:证明您预期的行为发生了,例如当您在四天内给它Post()时,您希望:

    • 告诉myRepository添加值
    • myRepository被告知要添加一天四次

示例

由于我不完全确定测试的目的是什么,我不知道所有的代码,我将举一个我认为会很好的例子,并希望也能展示如何设置模拟(理想情况下你会这样做!)

此外,如果这是一个真正的单元测试,你通常会在每个断言/验证中争取一个测试,所以你不必调试测试,只需要调试失败的代码,但我在这里放三个用于“简单”

在这个测试中,你会看到我:

  • 关心测试POST中的逻辑
  • 所以我创建了一个模拟的存储库,仅用于验证它的名称,
  • 设置为在调用时适当响应的模拟请求
  • 我将模拟的存储库传递给Controller的构造函数(通过注入隔离)
  • 然后我在带有模拟协作者(存储库和请求)的实时控制器上执行我关心的操作value
  • 然后我验证POST按预期执行/行为。

    POST

你的目标不应该是让你的老板感到高兴你已经写了一个测试。相反,努力进行有价值和富有表现力的测试,以便您能够保持这种状态。你不会完美的(也不应该尝试)只是确保你正在测试的东西增加价值。涵盖如果他们要更改代码,那么您的测试将失败,表明您有错误。

我希望这会有所帮助,请回复评论/问题。