71

I'm in the process of building an ASP.NET Core WebAPI and I'm attempting to write unit tests for the controllers. Most examples I've found are from the older WebAPI/WebAPI2 platforms and don't seem to correlate with the new Core controllers.

My controller methods are returning IActionResults. However, the IActionResult object only has a ExecuteResultAsync() method which requires a controller context. I'm instantiating the controller manually, so the controller context in this instance is null, which causes an exception when calling ExecuteResultAsync. Essentially this is leading me down a very hacky path to get these unit tests to successfully complete and is very messy. I'm left wondering that there must be a more simple/correct way of testing API controllers.

Also, my controllers are NOT using async/await if that makes a difference.

Simple example of what I'm trying to achieve:

Controller method:

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

Unit test:

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

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

     // assert
     Assert.Equal(HttpStatusCode.OK, ????);
}
Nkosi
  • 191,971
  • 29
  • 311
  • 378
Jake Shakesworth
  • 2,575
  • 4
  • 19
  • 39
  • 1
    Show the `GetOrders` method. what are you returning in that method. cast the result to the type of what you are returning in the method and perform your assert on that. – Nkosi Dec 22 '16 at 23:35

6 Answers6

103

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);
}
Pavel Anikhouski
  • 18,232
  • 12
  • 32
  • 48
Nkosi
  • 191,971
  • 29
  • 311
  • 378
  • 3
    Hi again Nkosi :) Can I somehow compare the whole result object to an expected one, so I can check both return code and object? Right now, Assert.AreEqual(actual, expected) doesn't seem to work. Edit: The object is a string. Do I have to make to Asserts? – Squirrelkiller May 01 '18 at 13:47
  • @Squirrelkiller that would be because they are two separate instances. You could extract the object from the result and make your comparison on that provided you control both. – Nkosi May 01 '18 at 13:49
  • 1
    @Squirrelkiller for example `Assert.AreEquan(myObject, okResult.Value);` where `myObject` is assumed to be what was returned from your mock and passed to `Ok()` in the controller action. – Nkosi May 01 '18 at 13:52
  • The problem with this appears when you're returning just Ok() with no content and it's retuning an OkResult instead an OkObjectResult. – Mauro Bilotti Mar 23 '21 at 20:43
17

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

Ernest
  • 1,549
  • 16
  • 17
4

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);
}
n1k1t0ss
  • 771
  • 1
  • 5
  • 4
0

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://docs.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.

Jacek
  • 11
  • 2
0

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);
}
Alexandre N.
  • 2,096
  • 1
  • 24
  • 28
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`
}
Josef
  • 836
  • 10
  • 16