如何在 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.HttpContextControllerBase.ControllerContext 的 getter。这意味着您必须模拟 ControllerContext(以及嵌套的 HttpContext 和 HttpResponse)并在设置阶段将其分配给您的控制器。

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

我个人觉得除了我上面提到的open bug 之外太麻烦了。

我建议您将关闭逻辑(如果确实有必要)移至 IDisposable 范围内的服务并改为在 Dispose 中处理它 - 假设它不是会影响响应延迟的计算量大的操作。