在设置模拟到单元测试 WebAPI 帖子时遇到困难
本文关键字:遇到 WebAPI 设置 模拟 单元测试 | 更新日期: 2023-09-27 18:16:13
我正在尝试使用 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 的哪一部分的帮助将不胜感激(帮助我理解帖子只是锦上添花(。
我希望我不是在告诉你一些你已经知道的事情,但看起来你可能在质疑从哪里开始?这是测试中最难的部分...
为了确保我们在同一页面上,知道要测试的内容的关键是描述方案,而对该方案进行单元测试的关键是隔离。
这意味着您希望隔离"被测试"类。
此外,如果您先编写测试,然后再编写代码以使其通过,则代码更容易测试。您不是这种情况,因此这意味着您拥有的代码如果不更改它,可能无法测试。
最后,给定任何外部/第三方系统,除非您正在进行"探索性测试">,否则您不想测试第三方内容,即http发布/获取。相反,您希望测试代码及其性能。
假设你知道这一点或一切都有意义,那么,这部分也将是显而易见的。
Moq 或任何其他模拟框架旨在代表被测类与之协作的对象/服务,以便单独提供帮助。给定两个类,类 A 和类 B,其中类 A 作用于类 B,您希望在将类 B 提供给类 A 时伪造/模拟类 B,以便您可以断言/验证类 A 的行为是否符合您对给定场景的预期。乍一看这似乎很幼稚,但考虑到您也将对 ClassB 执行相同的操作,然后您有一套测试可以隔离它们正在测试的内容,这为您提供了很大的覆盖范围。
隔离的关键是注入,确保如果 ClassA 作用于 ClassB,你将 ClassB 传递给 ClassA 的构造函数,这样你就可以给它一个假的 B 类。
有些人不赞成更改代码以使其可测试,但我的论点是,如果您首先编写的代码是可测试的,那么您就不必更改它,因此请尝试重构而不是重新设计。
要测试的内容
因此,这意味着您将需要几个不同的场景,每个场景都有与您关心的内容隔离的每个测试。
良好测试的关键是弄清楚你想要测试什么,然后安排你的测试,以便清楚地知道你在做什么。
测试类名不需要包含"Test";这是多余的。解释一下场景是什么;谁参与其中等。
测试方法应该说明你关心测试的操作是什么;你处于什么状态,等等。
** 在方法中**现在遵循"安排,行动,断言">(又名给定,何时,然后(方法:
安排:在这里设置所有模拟或你需要的任何变量,包括你正在测试的一个类,比如你的真控制器,但假 myRepository 和假
value
行动:做实际动作,如
Post()
断言:证明你预期的行为发生了,比如当你给它一个四天的
value
时,那么你期望:- myRepository 被告知添加值
- myRepository 被告知要添加四次 一天
一个例子
由于我不完全确定测试的意图是什么,而且我不知道所有代码,我将举一个我认为会很好地相关的例子,并希望也能展示如何设置模拟(理想情况下,你会这样做!
此外,如果这真的是一个单元测试,你通常会争取每个断言/验证进行一次测试,这样你就不必调试测试,只需要调试失败的代码,但我在这里放了三个"简单"。
在此测试中,您将看到我:
- 关心在开机自检中测试逻辑
- 所以我创建了一个模拟存储库,仅用于验证它被调用,
- 以及设置为在调用时做出适当响应的模拟请求,
- 我将模拟的存储库传递给控制器的构造函数(通过注入隔离(
- 然后我
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 ); } }
关闭
你的目标不应该是让你的老板因为你写了测试而感到高兴。相反,努力进行有价值和富有表现力的测试,您将能够在未来保持。你不会让它变得完美(也不应该尝试(,只要确保你正在测试的东西增加价值。涵盖如果它们在代码中更改,您的测试将失败的内容,这表明您有错误。
我希望这有帮助,请回复评论/问题。