我有以下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 中处理它 - 假设它不是会影响响应延迟的计算密集型操作。