【问题标题】:Having difficulties setting up a mock to unit test WebAPI Post在设置模拟单元测试 WebAPI Post 时遇到困难
【发布时间】:2013-05-30 17:55:48
【问题描述】:

我正在尝试使用 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 只是锦上添花)。

【问题讨论】:

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


    【解决方案1】:

    一边

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

    为了确保我们在同一页面上,了解要测试什么的关键是描述场景,而单元测试该场景的关键是隔离。

    这意味着你想隔离类“被测试”

    此外,如果您先编写测试然后编写代码以使测试通过,则代码更容易测试。您没有处于这种情况,这意味着您拥有的代码可能无法在不更改的情况下进行测试。

    最后,给定任何外部/第 3 方系统,除非您正在进行 “探索性测试”,否则您不想测试第 3 方的东西,即 http 发布/获取。相反,您想测试您的代码及其执行情况。

    假设你知道这一切都说得通,那么这部分也是显而易见的。

    Moq 或任何其他模拟框架旨在代表您的被测类与之协作的对象/服务,以帮助隔离。给定两个类,ClassA 和 ClassB,其中 ClassA 作用于 ClassB,您希望在将 ClassA 提供给 ClassA 时伪造/模拟 Class B,以便您可以断言/验证 ClassA 在给定场景中的行为是否符合您的预期。起初这似乎很幼稚,但考虑到您也会对 ClassB 做同样的事情,然后您就有了一套测试来隔离他们正在测试的内容,这为您提供了很好的覆盖范围。

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

    有些人不赞成更改代码以使其可测试,但我的论点是,如果您首先编写可测试的代码,则不必更改它,因此请尝试重构而不是重新设计。

    测试什么

    所以,这意味着您将需要几个不同的场景,每个测试都与您关心的内容隔离开来。

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

    • 测试类名称不需要在其中包含“测试”;那是多余的。解释一下场景是什么;谁参与等等。

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

    • ** 在方法内部** 现在遵循 “Arrange, Act, Assert” (又名 Given, When, Then) 方法: p>

    • 安排:在此处设置所有模拟或您需要的任何变量,包括您正在测试的一个类,例如您的真实 Controller 但假 myRepository 和假 value

    • Act:做实际动作,如Post()

    • 断言:证明您预期的行为发生了,例如当您在四天后给它value,那么您预期:

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

    一个例子

    由于我不完全确定测试的目的是什么,而且我不了解所有代码,我将给出一个我认为会很好关联的示例,并希望也能说明如何设置模拟(理想情况下为什么会这样!)

    此外,如果这确实是一个单元测试,您通常会为每个断言/验证争取一个测试,这样您就不必调试测试,只需调试失败的代码,但我在这里放三个是为了“简单” .

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

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

      [TestClass]
      public class GivenAValidRequestAndRepository(){
      
        [TestMethod]
        public void WhenWeReceiveAPostRequest(){
      
          //Arrange / Given
          var repository = new Mock<IRepository>();
          var request = new Mock<IRequest>();
          request.Setup ( rq => rq.ToString() )
                 .Returns ( "This is valid json ;-)" );
          request.Setup ( rq => rq.Days )
                 .Returns ( new List<IDay> {
                   "Monday",
                   "Tuesday",
                  } );
         var controller = new RequestController( repository.Object );
      
      
      
         //Act / When
         int actual = controller.Post( request.Object );
      
      
         //Assert / Verify
         // - then we add the request to the repository
         repository.Verify( 
           repo => repo.AddRequest( request, Times.Once() );
         // - then we add the two days (from above setup) in the request to the repository
         repository.Verify( 
           repo => repo.AddRequestDays( It.IsAny<IDay>(), Times.Exactly( 2 ));
         // - then we receive a count indicating we successfully processed the request
         Assert.NotEqual( -1, actual );
        }
      }
      

    结束

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

    希望对您有所帮助,请回复 cmets / 问题。

    【讨论】:

    • 作为您结束时的简短评论,我的一些测试发现了一些错误、缺少的功能和时间问题。这是让我继续前进的动力。但是,在您的示例中,您模拟了 IRequest 和 IRepository。从我看过的其他示例中,这些将是要测试的类中的接口,对吗?因为能够添加这些接口,正如我在许多其他示例中看到的那样,这也是我面临的大部分方法可能被归类为“不可测试”的部分原因。
    • 亚当,抱歉回复慢,我正在度假。是的,我应该指定那些将是接口,这将是理想的情况。基本上,您希望尽可能地将失败隔离在您正在测试的课程中,并通过伪造其他所有内容来做到这一点。将接口传递给目标类 (a.k.a 组合) 的构造函数允许您执行 “依赖注入”,传递假货 intsead。当你没有这些接口时,你可以开始提取接口&lt;ctrl&gt;+R,I 或者如果你必须对具体类使用 Moq。
    猜你喜欢
    • 2012-10-06
    • 2021-11-05
    • 2022-07-26
    • 1970-01-01
    • 1970-01-01
    • 2021-11-20
    • 1970-01-01
    • 2013-03-24
    • 2012-08-01
    相关资源
    最近更新 更多