-1

I am moving to use and ActionResult rather than IActionResult so that Swagger automatically picks up my types, but I am getting an error saying that I cannot cast an ActionResult to an OkObjectResult.

How do I cast to an OKObjectResult for testing a 200 status code?

My IActionResult Controller

[HttpGet]
public async Task<IActionResult<IEnumerable<Listing>>> Get()
{
  var listings = await listingService.GetAllListings();
  if (listings.Any())
  {
    return Ok(listings);
  }
  return NotFound();
}

My ActionResult Controller

[HttpGet]
public async Task<ActionResult<IEnumerable<Listing>>> Get()
{
  var listings = await listingService.GetAllListings();
  if (listings.Any())
  {
    return Ok(listings);
  }
  return NotFound();
}

My Test

[Fact]
public async Task ShouldReturnA200StatusCode()
{
    var res = (OkObjectResult)await sut.Get();

    res.StatusCode.Should().Be(200);
}
iamsimonsmale
  • 366
  • 3
  • 14
  • You're testing the framework, not your SUT. If the cast to `OkObjectResult` succeeds, the status code will always be `200`. If it's not `200`, it will necessarily be a different type and the test will fail before the actual assertion. – madreflection Mar 25 '22 at 19:46
  • By "testing the framework", I mean that you're verifying that `OkObjectResult` will have a status code of `200`, a fact that should be (and I assume, is) tested by the ASP.NET Core unit tests, not your unit tests. Simply asserting that `res is OkObjectResult` is sufficient, although this doesn't test for all possible ways that it can return a `200` result (and neither does what you have now, for that matter). Either way, you're testing implementation details. – madreflection Mar 25 '22 at 19:47
  • Thanks for the feedback @madreflection. I am new to unit testing in C# and haven't yet found any good information on how to do this (currently I am put the pieces together after watching a few tutorials). I would love any resources you have have on doing this so I test the SUT. – iamsimonsmale Mar 25 '22 at 19:50
  • On a separate note, I'm not sure how/why Swagger would pick up your types any differently with `ActionResult` vs. `IActionResult`. Use the `ProducesResponseType` attribute and it will generate the appropriate schema information in the OAS3 definition (swagger.json). – madreflection Mar 25 '22 at 19:52
  • re: Swagger [the docs here](https://learn.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-6.0#actionresultt-type) say the advantage is that you not longer need the annotation (I might even be able to omit the `Ok()` part too) – iamsimonsmale Mar 25 '22 at 19:57
  • I would advise against omitting the explicit `Ok()` call... I was trying to track down an issue and the implicit conversion involved there was making things *very* difficult. In fact, I would advise that you be very explicit about your response codes if you're trying to make a truly RESTful API. It's easier to break from that standard if you don't. – madreflection Mar 25 '22 at 20:00
  • Yeah, this is one of the reasons I have left it in so far. All the other answers I am finding for testing a 200 status code all have casting. I would be grateful if you could provide an example of how you would test it – iamsimonsmale Mar 25 '22 at 20:02
  • I don't have one. A certain amount of implementation detail testing might be acceptable here. Assert that `res is OkObjectResult`, and then maybe verify that its `Value` property is of the expected type. The problem is that if you change how it returns its OK response, you break the test when the test probably shouldn't care about that detail. This touches on "leaky abstraction" territory. – madreflection Mar 25 '22 at 20:07
  • As for the answer you posted, it's not really that useful. You're basically just repeating the answer you linked but with the specifics of your code. The concept of a simple cast has been covered time and time again and doesn't need yet another answer. In fact, I'm tempted to flag it as a duplicate. – madreflection Mar 25 '22 at 20:21
  • [this is a good article](https://andrewlock.net/should-you-unit-test-controllers-in-aspnetcore/) discussing the brittleness of unit testing controllers. Thank you for the discussion, it has been very informative. – iamsimonsmale Mar 25 '22 at 21:14
  • Does this answer your question? [Get a Value from ActionResult in a ASP.Net Core API Method](https://stackoverflow.com/questions/61453820/get-a-value-from-actionresultobject-in-a-asp-net-core-api-method) – Michael Freidgeim Aug 10 '22 at 07:03

2 Answers2

1

Take a look about my solution about how to validate HTTP status code in unit test using XUnit.

[Fact]
public async Task UpdateProduct_When_Product_IsValid_ShouldReturn200()
{
    //Arrange
    ProductDto productDto = new DataGenerator()
        .GenerateProductDto_Valid(1)
        .First();

    var result = _productAppServiceMock.Setup(p => p
    .UpdateAsync(
        It.IsAny<Guid>(),
        It.IsAny<ProductDto>()))
    .ReturnsAsync(() => productDto);

    //Act
    var itemHttp = await productController
        .UpdateProductAsync(productDto.Id, productDto);

    //Assert
    _productAppServiceMock.Verify(p => p.UpdateAsync(It.IsAny<Guid>(),
        productDto), times: Times.Once);

    Assert.Equal(typeof(Microsoft.AspNetCore.Mvc.OkObjectResult), itemHttp.GetType());
}
Felipe Augusto
  • 1,341
  • 1
  • 16
  • 18
-1

Following the guidance of this answer to a similar question you need to cast the Result of the .Get() method (rather than just the .Get() method) and then you can check that for the 200 OK StatusCode


    [Fact]
    public async Task ShouldReturnA200StatusCode()
    {
        var res = await sut.Get();

        var okObj = res.Result as ObjectResult;

        okObj.StatusCode.Should().Be(StatusCodes.Status200OK);
    }

Following on from the comments above I have used the ObjectResult type with the aim to not coerce the 200 status code and have used the as casting.

iamsimonsmale
  • 366
  • 3
  • 14
  • 2
    Never use `as` and use the result without first testing that it's not `null`. If/when it yields `null`, the next line will throw a `NullReferenceException`. That doesn't describe the *actual* problem as a result of the test, so your test results will not accurately reflect the nature of the regression. – madreflection Mar 25 '22 at 21:02