如何在 finally 块中使用 Response.OnCompleted 委托进行测试
How to test with Response.OnCompleted delegate in a finally block
我有以下 netcore 2.2 控制器方法,我正在尝试为其编写 xUnit 集成测试:
private readonly ISoapSvc _soapSvc;
private readonly IRepositorySvc _repositorySvc;
public SnowConnectorController(ISoapSvc soapSvc, IRepositorySvc repositorySvc)
{
_soapSvc = soapSvc;
_repositorySvc = repositorySvc;
}
[Route("accept")]
[HttpPost]
[Produces("text/xml")]
public async Task<IActionResult> Accept([FromBody] XDocument soapRequest)
{
try
{
var response = new CreateRes
{
Body = new Body
{
Response = new Response
{
Status = "Accepted"
}
}
};
return Ok(response);
}
finally
{
// After the first API call completes
Response.OnCompleted(async () =>
{
// Run the close method
await Close(soapRequest);
});
}
}
catch 块 运行s 并做它需要的事情,然后 finally 块 运行s 并根据设计在 catch 中的请求完成后做它需要做的事情。
Close 一直都是私有方法。它以 public 控制器方法开始,但我不需要公开它的功能,因此将其移至私有方法状态。
这是我开始的集成测试,目的只是测试代码的 try 部分:
[Fact]
public async Task AlwaysReturnAcceptedResponse()
{
// Arrange------
// Build mocks so that we can inject them in our system under tests constructor
var mockSoapSvc = new Mock<ISoapSvc>();
var mockRepositorySvc = new Mock<IRepositorySvc>();
// Build system under test(sut)
var sut = new SnowConnectorController(mockSoapSvc.Object, mockRepositorySvc.Object);
var mockRequest = XDocument.Load("..\..\..\mockRequest.xml");
// Act------
// Form and send test request to test system
var actualResult = await sut.Accept(mockRequest);
var actualValue = actualResult.GetType().GetProperty("Value").GetValue(actualResult);
// Assert------
// The returned object from the method call should be of type CreateRes
Assert.IsType<CreateRes>(actualValue);
}
我是测试新手...我一直在编写测试并摸索着解决问题。我开始进入控制器方法并不知道它会去哪里。测试通过 try 方法进行,然后一旦它命中 finally 块中的委托就会抛出异常。
看来我的测试必须 运行 直到 finally 块的结果,除非有办法告诉它停止 catch 阻止执行?
很好,我正在学习,但现在对我来说这种方法的问题是 中的 HttpResponse Response.OnCompleted 委托最终 阻止 returns null 当我的测试是 运行ning 并且我没有成功地弄清楚我可以做些什么来不让它为 null - 因为它是 null,它抛出这个当我的单元测试正在执行时 -
System.NullReferenceException: 'Object reference not set to an instance of an object.'
*发生的一个想法是,如果我要使私有 Close 方法成为 public 控制器方法,然后使 Accept 方法没有 finally块,我可以创建第三个控制器方法,它通过 运行ning 两个控制器方法执行 try finally 操作,然后只测试单独的控制器方法,即 st运行g 和第三个方法。但是,它感觉不对,因为我只是为了单元测试而公开方法,我不需要公开 Close。
如果上述想法不是正确的方法,我想知道什么是正确的方法,如果我只需要端到端地测试,我将如何克服空 httpresponse?
如有任何想法,我们将不胜感激。谢谢 SO 社区!
编辑 - 已更新的测试在接受的答案实施后起作用。谢谢!
[Fact]
public async Task AlwaysReturnAcceptedResponse()
{
// Arrange------
// Build mocks so that we can inject them in our system under tests constructor
var mockSoapSvc = new Mock<ISoapSvc>();
var mockRepositorySvc = new Mock<IRepositorySvc>();
// Build system under test(sut)
var sut = new SnowConnectorController(mockSoapSvc.Object, mockRepositorySvc.Object)
{
// Supply mocked ControllerContext and HttpContext so that finally block doesnt fail test
ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext()
}
};
var mockRequest = XDocument.Load("..\..\..\mockRequest.xml");
// Act------
// Form and send test request to test system
var actualResult = await sut.Accept(mockRequest);
var actualValue = actualResult.GetType().GetProperty("Value").GetValue(actualResult);
// Assert------
// The returned object from the method call should be of type CreateRes
Assert.IsType<CreateRes>(actualValue);
}
想知道您在 Close 方法中对输入参数做了什么。
发送响应后是否必须发生?它可能并不总是如您预期的那样发生,请参阅 here。
尽管如此,在运行时 asp.net 核心运行时会在控制器上设置很多属性,包括 ControllerContext、HttpContext、Request、Response 等。
但是那些在单元测试中不可用,因为那里没有 asp.net 核心运行时。
如果你真的想测试这个,你将不得不嘲笑他们。
这是 ControllerBase source code.
正如我们所见,ControllerBase.Response
只是 returns ControllerBase.HttpContext.Response
,而 ControllerBase.HttpContext
是 ControllerBase.ControllerContext
的 getter。这意味着您必须模拟 ControllerContext(以及嵌套的 HttpContext 和 HttpResponse)并在设置阶段将其分配给您的控制器。
此外,OnCompleted 回调也不会在单元测试中被调用。如果您想对该部分进行单元测试,则必须手动触发它。
我个人觉得除了我上面提到的open bug 之外太麻烦了。
我建议您将关闭逻辑(如果确实有必要)移至 IDisposable 范围内的服务并改为在 Dispose 中处理它 - 假设它不是会影响响应延迟的计算量大的操作。
我有以下 netcore 2.2 控制器方法,我正在尝试为其编写 xUnit 集成测试:
private readonly ISoapSvc _soapSvc;
private readonly IRepositorySvc _repositorySvc;
public SnowConnectorController(ISoapSvc soapSvc, IRepositorySvc repositorySvc)
{
_soapSvc = soapSvc;
_repositorySvc = repositorySvc;
}
[Route("accept")]
[HttpPost]
[Produces("text/xml")]
public async Task<IActionResult> Accept([FromBody] XDocument soapRequest)
{
try
{
var response = new CreateRes
{
Body = new Body
{
Response = new Response
{
Status = "Accepted"
}
}
};
return Ok(response);
}
finally
{
// After the first API call completes
Response.OnCompleted(async () =>
{
// Run the close method
await Close(soapRequest);
});
}
}
catch 块 运行s 并做它需要的事情,然后 finally 块 运行s 并根据设计在 catch 中的请求完成后做它需要做的事情。
Close 一直都是私有方法。它以 public 控制器方法开始,但我不需要公开它的功能,因此将其移至私有方法状态。
这是我开始的集成测试,目的只是测试代码的 try 部分:
[Fact]
public async Task AlwaysReturnAcceptedResponse()
{
// Arrange------
// Build mocks so that we can inject them in our system under tests constructor
var mockSoapSvc = new Mock<ISoapSvc>();
var mockRepositorySvc = new Mock<IRepositorySvc>();
// Build system under test(sut)
var sut = new SnowConnectorController(mockSoapSvc.Object, mockRepositorySvc.Object);
var mockRequest = XDocument.Load("..\..\..\mockRequest.xml");
// Act------
// Form and send test request to test system
var actualResult = await sut.Accept(mockRequest);
var actualValue = actualResult.GetType().GetProperty("Value").GetValue(actualResult);
// Assert------
// The returned object from the method call should be of type CreateRes
Assert.IsType<CreateRes>(actualValue);
}
我是测试新手...我一直在编写测试并摸索着解决问题。我开始进入控制器方法并不知道它会去哪里。测试通过 try 方法进行,然后一旦它命中 finally 块中的委托就会抛出异常。
看来我的测试必须 运行 直到 finally 块的结果,除非有办法告诉它停止 catch 阻止执行?
很好,我正在学习,但现在对我来说这种方法的问题是 中的 HttpResponse Response.OnCompleted 委托最终 阻止 returns null 当我的测试是 运行ning 并且我没有成功地弄清楚我可以做些什么来不让它为 null - 因为它是 null,它抛出这个当我的单元测试正在执行时 -
System.NullReferenceException: 'Object reference not set to an instance of an object.'
*发生的一个想法是,如果我要使私有 Close 方法成为 public 控制器方法,然后使 Accept 方法没有 finally块,我可以创建第三个控制器方法,它通过 运行ning 两个控制器方法执行 try finally 操作,然后只测试单独的控制器方法,即 st运行g 和第三个方法。但是,它感觉不对,因为我只是为了单元测试而公开方法,我不需要公开 Close。
如果上述想法不是正确的方法,我想知道什么是正确的方法,如果我只需要端到端地测试,我将如何克服空 httpresponse?
如有任何想法,我们将不胜感激。谢谢 SO 社区!
编辑 - 已更新的测试在接受的答案实施后起作用。谢谢!
[Fact]
public async Task AlwaysReturnAcceptedResponse()
{
// Arrange------
// Build mocks so that we can inject them in our system under tests constructor
var mockSoapSvc = new Mock<ISoapSvc>();
var mockRepositorySvc = new Mock<IRepositorySvc>();
// Build system under test(sut)
var sut = new SnowConnectorController(mockSoapSvc.Object, mockRepositorySvc.Object)
{
// Supply mocked ControllerContext and HttpContext so that finally block doesnt fail test
ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext()
}
};
var mockRequest = XDocument.Load("..\..\..\mockRequest.xml");
// Act------
// Form and send test request to test system
var actualResult = await sut.Accept(mockRequest);
var actualValue = actualResult.GetType().GetProperty("Value").GetValue(actualResult);
// Assert------
// The returned object from the method call should be of type CreateRes
Assert.IsType<CreateRes>(actualValue);
}
想知道您在 Close 方法中对输入参数做了什么。 发送响应后是否必须发生?它可能并不总是如您预期的那样发生,请参阅 here。
尽管如此,在运行时 asp.net 核心运行时会在控制器上设置很多属性,包括 ControllerContext、HttpContext、Request、Response 等。 但是那些在单元测试中不可用,因为那里没有 asp.net 核心运行时。 如果你真的想测试这个,你将不得不嘲笑他们。 这是 ControllerBase source code.
正如我们所见,ControllerBase.Response
只是 returns ControllerBase.HttpContext.Response
,而 ControllerBase.HttpContext
是 ControllerBase.ControllerContext
的 getter。这意味着您必须模拟 ControllerContext(以及嵌套的 HttpContext 和 HttpResponse)并在设置阶段将其分配给您的控制器。
此外,OnCompleted 回调也不会在单元测试中被调用。如果您想对该部分进行单元测试,则必须手动触发它。
我个人觉得除了我上面提到的open bug 之外太麻烦了。
我建议您将关闭逻辑(如果确实有必要)移至 IDisposable 范围内的服务并改为在 Dispose 中处理它 - 假设它不是会影响响应延迟的计算量大的操作。