如何在 finally 块中使用 Response.OnDone 委托进行测试



我有以下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块运行并执行它需要做的事情,然后 final块运行并在catch中的请求完成后执行它需要做的事情。

关闭既是一种私人方法。它最初是一个公共控制器方法,但我不需要公开它的功能,所以把它移到了私有方法状态。

这是我开始的一个集成测试,目的是测试代码的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块中的委托时引发异常。

看起来我的测试必须运行到最终块的结果,除非有办法告诉它停止执行catch块?

没关系,我正在学习,但是现在这种方法对我来说的问题是,当我的测试运行时,finally块中的HttpResponse 的 Response.OnCompleted委托返回 null,并且我还没有成功地弄清楚我能做些什么来不让它为空 - 因为它是空的,当我的单元测试正在执行时它会抛出这个 -

System.NullReferenceException: 'Object reference not set to an instance of an object.'

*发生的一个想法是,如果我要使私有 Close 方法成为公共控制器方法,然后使 Accept 方法没有 finally 块,我可以创建第三个控制器方法,该方法通过运行两个控制器方法来执行 tryfinally操作,然后只测试与第三个控制器方法串在一起的各个控制器方法。但是,这感觉不对,因为我只是为了单元测试而公开方法,而且我不需要公开 Close。

如果上述想法不是正确的方法,我想知道是什么,如果我只需要端到端测试,我将如何克服空 httpresponse?

任何想法将不胜感激。谢谢你,所以社区!

编辑 -更新了在实施接受答案后工作的测试。谢谢!

[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 方法中针对输入参数做什么。 它是否必须在发送响应后发生?它可能并不总是像您期望的那样发生,请参阅此处。

无论如何,在运行时 asp.net 核心运行时在控制器上设置了许多属性,包括 ControllerContext、HttpContext、Request、Response 等。 但是这些在单元测试中不可用,因为那里没有 asp.net 核心运行时。 如果你真的想测试这个,你将不得不嘲笑他们。 这是控制器基础源代码。

如我们所见,ControllerBase.Response只是返回ControllerBase.HttpContext.Response,而ControllerBase.HttpContext是来自ControllerBase.ControllerContext的获取者。这意味着您必须模拟 ControllerContext(以及嵌套的 HttpContext 以及 HttpResponse(,并在设置阶段将其分配给您的控制器。

此外,OnCompleted 回调也不会在单元测试中被调用。如果要对该部件进行单元测试,则必须手动触发它。

我个人认为除了我上面提到的开放错误之外,这太麻烦了。

我建议您将关闭逻辑(如果确实有必要(移动到 IDisposable 范围服务,并在 Dispose 中处理它 - 假设它不是会影响响应延迟的计算密集型操作。

最新更新