Get The Values From Task<IActionResult> & IActionResult Returned Through An API For Unit Testing


I have created an API using ASP.NET MVC Core 7. I recently began working on an ASP.NET Core project, specifically a charge station finding app. For unit testing, I'm utilizing Xunit. The methods we're testing return types of Task<IActionResult> and IActionResult. In this post, I'll discuss how to retrieve values from both types of return values when testing APIs.

When testing an API endpoint that returns a Task, we need to extract the values from the ActionResult returned by the endpoint in order to verify the correctness of the response. Let's understand with na example using a DoctorController and an API endpoint for retrieving a doctor by ID.

Let's assuming our DoctorController looks something like this:


using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

[ApiController]
[Route("api/[controller]")]
public class DoctorController : ControllerBase
{
    private readonly IDoctorService _doctorService;

    public DoctorController(IDoctorService doctorService)
    {
        _doctorService = doctorService;
    }

    [HttpGet("{id}")]
    public async Task GetDoctorById(int id)
    {
        var doctor = await _doctorService.GetDoctorByIdAsync(id);

        if (doctor == null)
        {
            return NotFound();
        }

        return Ok(doctor);
    }
}
  

Now, to test this endpoint and extract the values returned by the API, we can use unit testing. 

using Xunit;
using MyProject.Controllers;
using MyProject.Services;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Moq;

public class DoctorControllerTests
{
    [Fact]
    public async Task GetDoctorById_Returns_Ok_With_Correct_Doctor()
    {
        // Arrange
        int doctorId = 1;
        var mockDoctorService = new Mock();
        var expectedDoctor = new Doctor
        {
            Id = doctorId,
            Name = "Dr. John Doe",
            Specialization = "Cardiology",
            Country = "USA",
            CreatedAt = DateTime.Now
        };
        mockDoctorService.Setup(service => service.GetDoctorByIdAsync(doctorId))
                         .ReturnsAsync(expectedDoctor);
        var controller = new DoctorController(mockDoctorService.Object);

        // Act
        var actionResult = await controller.GetDoctorById(doctorId);

        // Assert
        var okObjectResult = actionResult as OkObjectResult;
        Assert.NotNull(okObjectResult);
        var actualDoctor = okObjectResult.Value as Doctor;
        Assert.NotNull(actualDoctor);
        Assert.Equal(expectedDoctor.Id, actualDoctor.Id);
        Assert.Equal(expectedDoctor.Name, actualDoctor.Name);
        Assert.Equal(expectedDoctor.Specialization, actualDoctor.Specialization);
        Assert.Equal(expectedDoctor.Country, actualDoctor.Country);
        Assert.Equal(expectedDoctor.CreatedAt, actualDoctor.CreatedAt);
    }
}
  

In this unit test, we set up a mock implementation of IDoctorService to simulate the behavior of our service layer. We then define an expected doctor object and configure the mock service to return this object when the GetDoctorByIdAsync method is called with a specific ID.

We call the GetDoctorById action method on the DoctorController instance and capture the IActionResult returned by the endpoint andthen perform operation to ensure that the response is as expected.

3

Let's see How to extract values from an IActionResult returned by an API for unit testing, using an example with an EmployeeController and CRUD operations for fields such as Id, Name, Email, Department, Salary, Date of Birth (DOB), and CreatedAt. Let's understand this process with an example using an EmployeeController and APIs for CRUD operations, including fields such as Id, Name, Email, Department, Salary, Date of Birth (DOB), and CreatedAt.

EmployeeController contains APIs for CRUD operations:


using Microsoft.AspNetCore.Mvc;
using System;

[ApiController]
[Route("api/[controller]")]
public class EmployeeController : ControllerBase
{
    private readonly IEmployeeService _employeeService;

    public EmployeeController(IEmployeeService employeeService)
    {
        _employeeService = employeeService;
    }

    [HttpGet("{id}")]
    public IActionResult GetEmployeeById(int id)
    {
        var employee = _employeeService.GetEmployeeById(id);

        if (employee == null)
        {
            return NotFound(); // If employee with specified id is not found
        }

        return Ok(employee); // Return employee if found
    }

  
}
  

To test this endpoint and retrieve the values from the returned IActionResult, we can use this unit testing.


using Xunit;
using MyProject.Controllers;
using MyProject.Services;
using Microsoft.AspNetCore.Mvc;
using System;
using Moq;

public class EmployeeControllerTests
{
    [Fact]
    public void GetEmployeeById_Returns_Ok_With_Correct_Employee()
    {
        // Arrange
        int employeeId = 1;
        var mockEmployeeService = new Mock();
        var expectedEmployee = new Employee
        {
            Id = employeeId,
            Name = "John Doe",
            Email = "[email protected]",
            Department = "Engineering",
            Salary = 50000,
            Dob = new DateTime(1990, 5, 15),
            CreatedAt = DateTime.Now
        };
        mockEmployeeService.Setup(service => service.GetEmployeeById(employeeId))
                           .Returns(expectedEmployee);
        var controller = new EmployeeController(mockEmployeeService.Object);

        // Act
        var actionResult = controller.GetEmployeeById(employeeId);

        // Assert
        var okObjectResult = actionResult as OkObjectResult;
        Assert.NotNull(okObjectResult);
        Assert.Equal(200, okObjectResult.StatusCode); // Ensure response status code is 200 (OK)

        var employee = okObjectResult.Value as Employee;
        Assert.NotNull(employee); // Ensure an Employee object is returned
        Assert.Equal(employeeId, employee.Id); // Ensure correct Employee ID is returned
        Assert.Equal(expectedEmployee.Name, employee.Name); // Assert other properties accordingly
        Assert.Equal(expectedEmployee.Email, employee.Email);
        Assert.Equal(expectedEmployee.Department, employee.Department);
        Assert.Equal(expectedEmployee.Salary, employee.Salary);
        Assert.Equal(expectedEmployee.Dob, employee.Dob);
        Assert.Equal(expectedEmployee.CreatedAt, employee.CreatedAt);
    }
}
  

In this unit test, we set up a mock implementation of IEmployeeService to simulate the behavior of our service layer and here we define an expected employee object and configure the mock service to return object when the GetEmployeeById method is called with a specific ID.

After arranging the test setup, we call the GetEmployeeById action method on the EmployeeController instance and capture the IActionResult returned by the endpoint.

3

I have created an API using ASP.NET MVC Core 6. One of my HttpGet methods is configured as follows:

        
            public async Task GetUserDetail([FromRoute] int? id)
            {
                try
                {
                    var dbContext = GetDbContext(); // Assuming this method retrieves the database context

                    // Logic for getting User from the database
var configuration = await dbContext.Users.FirstOrDefaultAsync(c => c.Id == id); if (configuration == null) { return NotFound(); // If User with specified id is not found
} return Ok(configuration); // Return configuration if found } catch (Exception ex) { // Handle exceptions here, if necessary return StatusCode(500, "An error occurred while processing your request."); } }

In api , we assume there's a table named Users in the database ,we retrieve the users corresponding to the provided id from the database and return it as Ok if found. If no User is found for the given id, we return NotFound and exceptions are handled by returning a 500 status code with an appropriate error message.

When unit testing this, I can verify that the response is Ok, but it's diffcult for me to inspect the values of the User returned.


        
            [TestMethod] 
            public void Test_GetUserDetail_ReturnsUser()
{ var dbContext = GetDbContext(); var controller = new UserController(dbContext); var itemCountBefore = dbContext.Users.Count(); var actionResult = controller.GetUserDetail(10); Assert.IsTrue(true); dbContext.Dispose(); }

However, I'm encountering difficulty achieving this with the following code, to solve the issue when unit testing the GetUserDetail method in the UserController,  we need to changed the return type of the Test_GetUserDetail_ReturnsUser method to Task to allow asynchronous.


using Xunit;
using MyProject.Controllers;
using MyProject.Services;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Moq;

public class UserControllerTests
{
    [Fact]
    public async Task Test_GetUserDetail_ReturnsUser()
    {
        // Arrange
        int userId = 10;
        var dbContext = GetDbContext();
        var controller = new UserController(dbContext);
        var itemCountBefore = dbContext.Users.Count();

        // Act
        var actionResult = await controller.GetUserDetail(userId);
        var okObjectResult = actionResult as OkObjectResult;

        // Assert
        Assert.NotNull(okObjectResult);
        Assert.Equal(200, okObjectResult.StatusCode); // Ensure response status code is 200 (OK)

        var user = okObjectResult.Value as User;
        Assert.NotNull(user); // Ensure a User object is returned
        Assert.Equal(userId, user.Id); // Ensure correct User ID is returned

        dbContext.Dispose();
    }
}
  

In this unit test code, we've made the following changes:

  • Changed the return type of the Test_GetUserDetail_ReturnsUser() method to Task to allow asynchronous  
  • Added async keyword to the test method to allow asynchronous execution.
  • Used await keyword when calling the GetUserDetail method to asynchronously retrieve the action result and asserted that the StatusCode of the returned OkObjectResult is 200 (OK).
  • Extracted the User object from the Value property of the OkObjectResult and asserted its properties.

Using these changes allow us to inspect the values of the User object returned by the GetUserDetail method and ensure its correctness during unit testing.