Graf123456
Graf123456

Reputation: 103

Unit Testing a Controller method returns null

I'm trying to learn Unit testing in .NET 6 by testing a controller function GetProduct. The problem is I get null returned in the variable var product = await _productController.GetProduct(productId);. As you can see in the picture below, the Result is ok but the Value, where the ServiceResponse<Product> was suppose to be is null.

enter image description here

Here is the controller function:

public class ProductController : ControllerBase
{
    private readonly IProductService _productService;

    public ProductController(IProductService productService)
    {
        _productService = productService;
    }

    [HttpGet("{productId}")]
    public async Task<ActionResult<ServiceResponse<Product>>> GetProduct(int productId)
    {
        var result = await _productService.GetProductAsync(productId);
        return Ok(result);
    }
}

Here is the Test:

public class ProductControllerTest
{
    private readonly ProductController _productController;
    private readonly Mock<IProductService> _productService = new Mock<IProductService>();

    public ProductControllerTest()
    {
        _productController = new ProductController(_productService.Object);
    }    

    [Test]
    public async Task GetProducts_ReturnsProduct_IfProductExists()
    {
        //Arange
        var productId = 1;
        var prodData = new Product
        {
            Id = productId,
            Title = "null"            
        };
        var prductResponse = new ServiceResponse<Product>
        {
            Data = prodData,
            Success = true ,
            Message = ""
        };

        _productService.Setup(x => x.GetProductAsync(productId)).ReturnsAsync(prductResponse);

        //Act
        var product = await _productController.GetProduct(productId);

        //Assert
        Assert.That(product?.Value?.Data?.Id, Is.EqualTo(productId));
    }

}

Upvotes: 0

Views: 970

Answers (2)

Akhilesh Bangalore
Akhilesh Bangalore

Reputation: 189

This behavior is observed due to overloaded operators on ActionResult<T> class.

Since the method, OK(value) returns an instance of OKObjectResult and the return type of controller method is of type ActionResult<ServiceResponse<Product>> the returned OKObjectResult instance is wrapped in an instance of ActionResult<T> and is exposed by the Result property. Hence typecasting the Result property (as shown by @BennyM) to OKObjectResult works.

Please note that the assertion would have succeeded had the controller method returned the ServiceResponse<Product> directly without modifying the return type on controller's method.

While this explains the behavior, I personally feel there is a better way to test controllers. This MSDN Article explains about integration testing. One can effectively unit test all the dimensions of the controllers - authentication, validation, (de)serialization, etc - by mocking the immediate dependencies of the respective controllers.

Upvotes: 1

BennyM
BennyM

Reputation: 2856

Since you are returning with an Ok call in your controller you can add a cast to the unit test.

   var result = (await _productController.GetProduct(productId)).Result as OkObjectResult;
   Assert.IsNotNull(result);
   var returnedServiceResponse = result.Value as ServiceResponse<Product>;
   Assert.That(returnedServiceResponse ?.Data?.Id, Is.EqualTo(productId));

Also you don't have to use Actionresult. You can also just return your service response

   [HttpGet("{productId}")]
    public async Task<ServiceResponse<Product>> GetProduct(int productId)
    {
        var result = await _productService.GetProductAsync(productId);
        return result;
    }

This will also make the test a bit easier as no need to use the OkObjectResult.

Upvotes: 1

Related Questions