【问题标题】:Is it possible to resolve the `StatusCode` of the `ObjectResult` base of the run-time derived class T value?是否可以解析运行时派生类 T 值的“ObjectResult”基础的“StatusCode”?
【发布时间】:2023-03-31 09:02:01
【问题描述】:

一些上下文:

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

MyController : BaseController
{
    public void IActionResult> Get(...)
    {
        try
        {
            if (...) retrun BadRequest(...)

            return Ok(...);
        }
        catch (Exception ex)
        {
            return StatusCode(500, ...);
        }
    }

例如,在每个单元测试结束时,我断言实际返回类型是 ObjectResultHttpCode 为 500,或者第二个单元测试的返回类型是 BadRequestObjectResultHttpCode 400:

[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);
}

我的问题:

不是将httpCode作为每个单元测试的参数发送,我想知道是否可以解析运行时T值的ObjectResult基数的StatusCode? 所以AssertHttpCode 函数的客户端,不需要发送httpCode 作为参数,比如:

public void ControllerTest_WhenRequestNotValid_ShouldRetrunBadRequest()
{
    ...
    IActionResult actual = controller.Get(...);
    //Assert
    AssertHttpCode<BadRequestObjectResult>(actual); //no 400 here, it is obvious!
}

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

【问题讨论】:

  • 可能与问题无关,但如果你有一个全局异常处理程序和一个模型验证器(都附带 ASP.NET Core),你可能不需要任何这些
  • 至于问题本身,可以查看T的类型,BadRequestObjectResult总会有400的结果
  • 嘿@CamiloTerevinto,您是否建议硬编码检查T 的类型是否为BadRequestObjectResult,然后检查它是否为400?
  • @CamiloTerevinto 我更喜欢你的第一条评论!我会用的。

标签: c# unit-testing generics asp.net-core-webapi


【解决方案1】:

最终我发现Type-Pattern (doc) 在这种情况下非常方便(感谢@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 将提供不同的 响应中的状态代码。

对断言进行硬编码会使单元测试在框架更新时中断。

【讨论】:

  • 您可以只使用T actual 并丢失类型检查。
  • 对不起,您说的类型检查丢失是什么意思?
【解决方案2】:

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

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

除此之外,您的测试应该在他们期望的输出中明确(通常的格式也是Assert(expected, actual),所以切换这些参数)。如果某些框架更新更改了某种响应类型的状态码怎么办?您的测试将接受该类型使用的状态代码,因此您的测试不会中断,而您的 API 将在其响应中提供不同的状态代码。

【讨论】:

  • 嘿@codecaster,感谢您的回答(+1),请您在下面查看我的回答以确保
【解决方案3】:

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

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 个错误创建一个新类来解决您在 cmets 中的问题。

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

【讨论】:

  • 这是一个不错的尝试 (+1),但恐怕 ObjectResult 没有无参数构造函数。
  • 它仅适用于 AssertHttpCode&lt;BadRequestObjectResult&gt;(actual); ,当我尝试使用 AssertHttpCode&lt;ObjectResult&gt;(actual); 断言 500 时它不起作用,激活器会正确创建 ObjectResult 类,但它会以空或空值。
  • 值得一提的是,ObjectResult 是基类,而其他派生类正在正确初始化。
  • 正确,基本ObjectResult 的初始状态代码为空,所以从技术上讲,这就是解析代码。我猜你正在使用这种类型的 500 错误?
  • 好的,您可以为 500 个错误创建一个新类,而不是使用基类。 (见上面的代码)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-02-10
  • 2013-03-03
  • 1970-01-01
  • 2013-08-21
  • 2011-11-09
  • 2023-03-11
  • 1970-01-01
相关资源
最近更新 更多