How to get the Values from a Task<IActionResult> returned through an API for Unit Testing

48,098

Solution 1

You can get tested controller without changing returned type.
IActionResult is base type for all others.
Cast result into expected type and compare returned value with expected.

Since you are testing asynchronous method, make test method asynchronous as well.

[TestMethod] 
public async Task ConfigurationSearchGetTest()
{
    using (var context = GetContextWithData())
    {
        var controller = new ConfigurationSearchController(context);
        var items = context.Configurations.Count();

        var actionResult = await controller.GetConfiguration(12);

        var okResult = actionResult as OkObjectResult;
        var actualConfiguration = okResult.Value as Configuration;

        // Now you can compare with expected values
        actualConfuguration.Should().BeEquivalentTo(expected);
    }
}

Solution 2

Good practice would suggest that you don't have a lot of code in your controller actions to test and the bulk of logic is in decoupled objects elsewhere that are much easier to test. Having said that, if you still want to test your controllers then you need to make your test async and await the calls.

One of the problems you will have is that you are using IActionResult as it allows you to return BadRequest(...) and Ok(...). However, since you are using ASP.NET MVC Core 2.1, you may want to start using the new ActionResult<T> type instead. This should help with your testing because you can now get direct access to the strongly typed return value. For example:

//Assuming your return type is `Configuration`
public async Task<ActionResult<Configuration>> GetConfiguration([FromRoute] int? id)
{
    try
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        ..... // Some code here

        // Note we are now returning the object directly, there is an implicit conversion 
        // done for you
        return configuration;
    }
    catch (Exception ex)
    {
        ... // Some code here
    }
}

Note we are now returning the object directly as there is an implicit conversion from Foo to ActionResult<Foo>

Now your test can look like this:

[TestMethod] 
public async Task ConfigurationSearchGetTest()
{
    var context = GetContextWithData();
    var controller = new ConfigurationSearchController(context);
    var items = context.Configurations.Count();

    // We now await the call
    var actionResult = await controller.GetConfiguration(12);

    // And the value we want is now a property of the return
    var configuration = actionResult.Value;

    Assert.IsTrue(true);
    context.Dispose();
}

Solution 3

As my reputation does not allow me to comment on @DavidG answer which goes in the right direction, I will put a sample on how to get the value inside Task<IActionResult>.

As @ Christopher J. Reynolds pointed out, actionResult.Value can be seen at runtime but not at compilation.

So, I'll show a basic test in which get the Values:

[TestMethod]
public async Task Get_ReturnsAnArea()
{
    // Arrange
    string areaId = "SomeArea";
    Area expectedArea = new Area() { ObjectId = areaId, AreaNameEn = "TestArea" };

    var restClient = new Mock<IRestClient>();
    restClient.Setup(client => client.GetAsync<Area>(It.IsAny<string>(), false)).ReturnsAsync(expectedArea);

    var controller = new AreasController(restClient.Object);

    //// Act

    // We now await the call
    IActionResult actionResult = await controller.Get(areaId);

    // We cast it to the expected response type
    OkObjectResult okResult = actionResult as OkObjectResult;

    // Assert

    Assert.IsNotNull(okResult);
    Assert.AreEqual(200, okResult.StatusCode);

    Assert.AreEqual(expectedArea, okResult.Value);

   // We cast Value to the expected type
    Area actualArea = okResult.Value as Area;
    Assert.IsTrue(expectedArea.AreaNameEn.Equals(actualArea.AreaNameEn));
}

For sure this could be improved but I just wanted to show you a simple way to get it.

I hope it helps.

Solution 4

You need to await the call to GetConfiguration to get the IActionResult object back as follows:

var actionResult = await controller.GetConfiguration(12);

To do this you need to change the signature of your test method to be async as well. So change this:

public void ConfigurationSearchGetTest()

To this:

public async Task ConfigurationSearchGetTest()
Share:
48,098
Christopher J. Reynolds
Author by

Christopher J. Reynolds

Updated on June 05, 2021

Comments

  • Christopher J. Reynolds
    Christopher J. Reynolds almost 3 years

    I have created an API using ASP.NET MVC Core v2.1. One of my HttpGet methods is set up as follows:

    public async Task<IActionResult> GetConfiguration([FromRoute] int? id)
    {
        try
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
    
            ..... // Some code here
    
            return Ok(configuration);
        }
        catch (Exception ex)
        {
            ... // Some code here
        }
    }
    

    When unit testing this I can check that Ok was the response, but I really need to see the values of the configuration. I don't seem to be able to get this to work with the following:

    [TestMethod] 
    public void ConfigurationSearchGetTest()
    {
        var context = GetContextWithData();
        var controller = new ConfigurationSearchController(context);
        var items = context.Configurations.Count();
        var actionResult = controller.GetConfiguration(12);
    
        Assert.IsTrue(true);
        context.Dispose();
    }
    

    At runtime, I can check that actionResult has certain values that I am unable to code for. Is there something I am doing wrong? Or am I just thinking about this wrong? I would like to be able to do:

    Assert.AreEqual(12, actionResult.Values.ConfigurationId);