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

我正在构建一个 ASP.NET Core WebAPI,并尝试为控制器编写单元测试。我发现的大多数示例都来自旧的 WebAPI/WebAPI2平台,似乎与新的 Core 控制器没有关联。

我的控制器方法返回 IActionResults。但是,IActionResult对象只有一个需要控制器上下文的 ExecuteResultAsync()方法。我正在手动实例化控制器,因此此实例中的控制器上下文为 null,这在调用 ExecuteResultAsync时会导致异常。从本质上讲,这将我带入一条非常复杂的道路,使这些单元测试能够成功完成,这是非常混乱的。我想知道测试 API 控制器一定有更简单/更正确的方法。

此外,我的控制器不使用异步/等待,如果这有什么不同。

举个简单的例子来说明我正在努力实现的目标:

控制器方法:

[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, ????);
}
72862 次浏览

Assuming something like the

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

the controller in this case is returning an OkObjectResult class.

Cast the result to the type of what you are returning in the method and perform your assert on that

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

You can also do cool things like:

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

Thanks

You also can use ActionResult class as a controller result (assuming you have type Orders). In that case you can use something like this:

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

and now in unit tests you have:

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

Besides, this is the recommendation of Microsoft - https://learn.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-2.2#actionresultt-type

Unfortunately, I don't know why using Ok method

return Ok(service.GetOrders());

doesn't map it properly.

Other answers adviced to cast to ObjectResult, but its work only if you return OkObjectResult \ NotFoundObjectResult \ etc. But server could return NotFound\ OkResult which derived from StatusCodeResult.

For example:

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

I looked at the implementation of all these methods and found that they are all inherited from the IStatusCodeActionResult interface. It seems like this is the most base type that contains 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);
}

A good way to do that is like this:

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