是否可以解析运行时派生类T值的ObjectResult基数的StatusCode?

问题描述

某些上下文:

我正在对一个简单的MyController进行单元测试,对于每项测试,我都断言http响应对象是Microsoft.AspNetCore.Mvc.ObjectResult基类的特定类型或其任何派生类,例如{ {1}}或BadRequestObjectResult

OkObjectResult

例如,在每个单元测试结束时,我断言实际的返回类型为MyController : BaseController { public void IActionResult> Get(...) { try { if (...) retrun BadRequest(...) return Ok(...); } catch (Exception ex) { return StatusCode(500,...); } } ,其中ObjectResult为500,或者第二个单元测试的返回类型为{{1} },其中HttpCode为400:

BadRequestObjectResult

这里是用于类型转换和声明HttpCode的断言功能

[Test]
public void ControllerTest_WhenFail_ShouldRetrunInternalServerError()
{
    ...
    IActionResult actual = controller.Get(...);
    //Assert
    AssertHttpCode<ObjectResult>(actual,500);
}

public void ControllerTest_WhenRequestNotValid_ShouldRetrunBadRequest()
{
    ...
    IActionResult actual = controller.Get(...);
    //Assert
    AssertHttpCode<BadRequestObjectResult>(actual,400);
}

我的问题:

而不是从每个单元测试中发送StatusCode作为参数,我想知道是否有可能解析运行private void AssertHttpCode<T>(IActionResult actual,int httpCode) where T : ObjectResult { Assert.IsNotNull(actual); T objectResult = actual as T; Assert.AreEqual(objectResult.StatusCode,httpCode); } 的{​​{1}} -time T值?,因此httpCode函数的客户端将不需要发送StatusCode作为参数,例如:

ObjectResult

解决方法

否,由于ObjectResult类型与状态代码之间没有一对一的关系,因此无法确定某个ObjectResult派生类型将其分配给其StatusCode property的状态代码。

因此,一个实现可以具有多个代码路径,这些路径根据某些条件分配不同的状态代码,另一个实现可以(仅)具有用户提供的状态代码,并且 some 始终返回相同的代码(除非从用户代码)。

此外,您的测试应明确显示期望的输出(通常的格式也为Assert(expected,actual),因此请切换这些参数)。如果某些框架更新更改了某个响应类型的状态代码怎么办?您的测试将接受该类型使用的状态代码,因此当API在响应中提供不同的状态代码时,测试不会中断。

,

如果要检查的对象结果类型具有无参数构造函数,则可以将其添加到约束中并执行类似的操作。

private void AssertHttpCode<T>(IActionResult actual) where T : ObjectResult,new()
{
    Assert.IsNotNull(actual);
    T objectResult = actual as T;
    var t = new T();
    Assert.AreEqual(objectResult.StatusCode,t.StatusCode);
}

否则,您将必须进行一些模式匹配以查看其类型。

我相信大多数ObjectResult类型只需要传递一个对象即可创建它们。您可以试试看。

private void AssertHttpCode<T>(IActionResult actual) where T : ObjectResult
{
    Assert.IsNotNull(actual);
    T objectResult = actual as T;
    var t = Activator.CreateInstance(typeof(T),new object()) as T;
    Assert.AreEqual(objectResult.StatusCode,t.StatusCode);
}

您可以为500个错误创建一个新类,以在注释中解决您的问题。

public class InternalServerErrorObjectResult : ObjectResult
{
    public InternalServerErrorObjectResult(object value) : base(value)
    {
        StatusCode = 500;
    }
}
,

最终,我发现Type-Patterndoc)在这种情况下非常方便(感谢@CamiloTerevinto作为提示):

private void AssertHttpCode<T>(IActionResult actual) where T : ObjectResult
{
    Assert.IsNotNull(actual);
    T actualObjectResult = actual as T;
    switch (actualObjectResult)
    {
        case BadRequestObjectResult x:
            Assert.AreEqual(400,actualObjectResult.StatusCode);
            break;
        case OkObjectResult x:
            Assert.AreEqual(200,actualObjectResult.StatusCode);
            break;
        case UnauthorizedObjectResult x:
            Assert.AreEqual(401,actualObjectResult.StatusCode);
            break;
        case CreatedAtActionResult x:
            Assert.AreEqual(201,actualObjectResult.StatusCode);
            break;                    
        case ObjectResult x:
            Assert.AreEqual(500,actualObjectResult.StatusCode);
            break;
        default:
            throw new NotImplementedException();
    }            
}

这也回答了@codecaster的答案引起的关注:

如果某些框架更新更改了某些设备的状态码该怎么办 回应类型?您的测试将接受该类型的状态码 使用,因此您的测试不会中断,而您的API将提供不同的 状态码。

在框架更新的情况下,对断言进行硬编码会使单元测试中断。