测试返回IActionResult的控制器方法的单元测试

101

我正在构建一个ASP.NET Core WebAPI,并尝试为控制器编写单元测试。我找到的大多数示例来自旧的WebAPI/WebAPI2平台,似乎与新的Core控制器不相符。

我的控制器方法返回 IActionResults。然而,IActionResult对象只有一个 ExecuteResultAsync() 方法,需要一个控制器上下文。我手动实例化了控制器,所以在这种情况下控制器上下文是 null,调用 ExecuteResultAsync 时会导致异常。本质上,这让我走上了一条非常难以处理的路径,以便成功完成这些单元测试,并且非常混乱。我对于是否存在更简单/正确的测试API控制器的方法感到困惑。

另外,请注意我的控制器没有使用async/await。

我想要实现的简单示例:

控制器方法:

[HttpGet(Name = "GetOrdersRoute")]
public IActionResult GetOrders([FromQuery]int page = 0)
{
     try
     {
        var query = _repository.GetAll().ToList();

        int totalCount = query.Count;
        int totalPages = (int)Math.Ceiling((double)totalCount / pageSize) - 1;
        var orders = query.Skip(pageSize * page).Take(pageSize);

        return Ok(new
        {
           TotalCount = totalCount,
           TotalPages = totalPages,

           Orders = orders
        });
     }
     catch (Exception ex)
     {
        return BadRequest(ex);
     }
}

单元测试:

[Fact]
public void GetOrders_WithOrdersInRepo_ReturnsOk()
{
     // arrange
     var controller = new OrdersController(new MockRepository());

     // act
     IActionResult result = controller.GetOrders();

     // assert
     Assert.Equal(HttpStatusCode.OK, ????);
}

1
显示GetOrders方法。你在该方法中返回什么?将结果转换为方法返回的类型并在其上执行assert。 - Nkosi
6个回答

151

假设类似于

public IActionResult GetOrders() {
    var orders = repository.All();
    return Ok(orders);
}

在这种情况下,控制器返回一个 OkObjectResult 类。

将结果强制转换为方法返回的类型,然后对其执行断言。

[Fact]
public void GetOrders_WithOrdersInRepo_ReturnsOk() {
    // arrange
    var controller = new OrdersController(new MockRepository());

    // act
    var result = controller.GetOrders();
    var okResult = result as OkObjectResult;

    // assert
    Assert.IsNotNull(okResult);
    Assert.AreEqual(200, okResult.StatusCode);
}

4
你好,Nkosi :) 我能否以某种方式比较整个结果对象与期望的对象,以便我可以检查返回代码和对象?目前, Assert.AreEqual<IActionResult>(actual, expected) 似乎不起作用。编辑:该对象是一个字符串。我需要进行两个Assert吗? - Squirrelkiller
@Squirrelkiller,这是因为它们是两个独立的实例。如果您控制两者,可以从结果中提取对象并进行比较。 - Nkosi
1
例如,@Squirrelkiller Assert.AreEquan(myObject, okResult.Value);,其中myObject被假设为从模拟返回并传递给控制器操作中的Ok()的内容。 - Nkosi
当你返回一个没有内容的Ok()时,问题就出现了,它会返回一个OkResult而不是OkObjectResult。 - Mauro Bilotti
当转换为OkObjectResult时,它的状态码总是200吗? - Cedervall

27
你还可以做一些很酷的事情,比如:

    var result = await controller.GetOrders();//
    var okResult = result as ObjectResult;

    // assert
    Assert.NotNull(okResult);
    Assert.True(okResult is OkObjectResult);
    Assert.IsType<TheTypeYouAreExpecting>(okResult.Value);
    Assert.Equal(StatusCodes.Status200OK, okResult.StatusCode);

谢谢


11

其他答案建议将其转换为ObjectResult,但只有当您返回OkObjectResult\NotFoundObjectResult\等时才适用。但是服务器可能返回从StatusCodeResult派生的NotFound\OkResult

例如:

public class SampleController : ControllerBase
{
    public async Task<IActionResult> FooAsync(int? id)
    {
        if (id == 0)
        {
            // returned "NotFoundResult" base type "StatusCodeResult"
            return NotFound();
        }

        if (id == 1)
        {
            // returned "StatusCodeResult" base type "StatusCodeResult"
            return StatusCode(StatusCodes.Status415UnsupportedMediaType);
        }

        // returned "OkObjectResult" base type "ObjectResult"
        return new OkObjectResult("some message");
    }
}

我查看了所有这些方法的实现,并发现它们都是从IStatusCodeActionResult接口继承而来。这似乎是包含StatusCode的最基础类型:

private SampleController _sampleController = new SampleController();

[Theory]
[InlineData(0, StatusCodes.Status404NotFound)]
[InlineData(1, StatusCodes.Status415UnsupportedMediaType)]
[InlineData(2, StatusCodes.Status200OK)]
public async Task Foo_ResponseTest(int id, int expectedCode)
{
    var actionResult = await _sampleController.FooAsync(id);
    var statusCodeResult = (IStatusCodeActionResult)actionResult;
    Assert.Equal(expectedCode, statusCodeResult.StatusCode);
}

2
一种好的方法是这样的:

像这样做:

[Fact]
public void GetOrders_WithOrdersInRepo_ReturnsOk() {
    // arrange
    var controller = new OrdersController(new MockRepository());

    // act
    var result = controller.GetOrders();

    // assert
    var okResult = Assert.IsType<OkObjectResult>(result);
    Assert.IsNotNull(okResult);
    Assert.AreEqual(200, okResult.StatusCode);
}

0
public async Task CallRxData_ReturnsHttpNotFound_ForInvalidJobNum_ReturnsStoredRxOrder()
{
    var scanInController = new ScanInController(_logger, _scanInService);
    var okResult = await scanInController.CallRxData(rxOrderRequest);
    var notFoundResult = await scanInController.CallRxData(invalidRxOrderRequest);
    var okResultWithScanInCheckFalse = await scanInController.CallRxData(rxOrderRequest);
    var okResultWithEmptyAelAntiFakeDatas = await scanInController.CallRxData(rxOrderRequest);
    // Assert
    Assert.That(okResult, Is.TypeOf<OkObjectResult>());
    Assert.That(notFoundResult, Is.TypeOf<NotFoundObjectResult>());
    Assert.IsFalse(((okResultWithScanInCheckFalse as ObjectResult).Value as RxOrder).IsSecurity);`enter code here`
}

0

你也可以将ActionResult类用作控制器结果(假设你有类型为Orders的类型)。在这种情况下,您可以使用以下内容:

[ProducesResponseType(typeof(Orders), StatusCodes.Status200OK)]
public ActionResult<Orders> GetOrders()
{
    return service.GetOrders();
}

现在在单元测试中你有:

Assert.IsInstanceOf<Orders>(result.Value);

此外,这是微软的建议 - https://learn.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-2.2#actionresultt-type 不幸的是,我不知道为什么要使用Ok方法。
return Ok(service.GetOrders());

没有正确映射。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接