简体   繁体   中英

How to create a unit test for an IActionResult controller action which returns decimal?

Im new to unittest and also confused i hope you help me understand couple of things, I would like to write a Xunit test for the following scenario:

I have a following controller:

    [ApiController]
    [Route("api/[controller]")]
     public class HomeController : ControllerBase
    {
        public readonly IDataService _dataService;
        public HomeController(IDataService dataservice)
        {

            _dataService = dataservice;
        }
        [HttpGet("/value")]
        public IActionResult GetTotal(string name, string year)
        {

            var totalAmount = _dataService.GetToTalValue(name, year);
            return Ok(totalAmount.Result);
        }

i have repository class to talk to db i named it dataservice:

 public  interface IDataService
{
  
    public Task<decimal> GetTotal(string name, string year)
}

the implemnation is simple i just write what im returning to make the long story shot:

        public Task<decimal> GetTotal(string name, string year){
    ///here i connect to db and get data and return to controller 

     return Task.FromResult(totalAmount);
    }

at the end,i have created a unit test by following some online tutorials:

public class UnitTest1
{
    [Fact]
    public void Test1()
    {
       var sut = new Mock<IDataService>();

        sut.Setup(s => s.GetTotal("ASK", "1235")).ReturnsAsync(1231);

        HomeController mm = new HomeController(sut.Object);

        var result = mm.GetTotal("ASK", "1235");

       ///asset gives error because cant convert iactionresult to decimal
    }
}

}

first i of all i don't get why do we need to write :

  sut.Setup(s => s.GetTotal("ASK", "1235")).ReturnsAsync(1231);

because we are going to the exact thing with controller again:

 HomeController mm = new HomeController(sut.Object);
 var result = mm.GetTotal("ASK", "1235");

that setup means im passing two values to the GetTotal and expect 1231 right?whats the point of testing the controller then? Please help me understand it and my second question is the result i get from iactionresult is decimal but in assert i get error which i cant convert iactionresult to decimal

any help will be highly appreciated

if you mock an interface you haven't to test the results of methods in the interface, you think that dataservice is working and force the result. this should be

sut.Setup(s => s.GetToTalValue("ASK", "1235")).ReturnsAsync(10);

you have to test other logic around this interface. for example if the controller pass correctly the parameters and return the correct value

[Fact]
public void Test1()
{
    decimal resultTotal = 10;
    string name = "ASK";
    string year = "1235";

    var mockDataService = new Mock<IDataService>();
        
    mockDataService.Setup(s => s.GetToTalValue(name, year)).ReturnsAsync(resultTotal);

    HomeController sut = new HomeController(mockDataService.Object);

    IActionResult result = sut.GetTotal(name, year);

    Assert.True(result is OkObjectResult);
    Assert.True((decimal)((result as OkObjectResult)?.Value) == resultTotal);
}

First question is very opinionated, and this is a very simple Action, I'd only write a test for "testing" sake and some regression testing, if more logic is added, I'd for sure write tests for the changes.

Second question, OK() is a convenience method for OkObjectResult , so you can cast it as so:

var result = (OkObjectResult) mm.GetTotal("ASK", "1235");

Assert.True(result.Value == 1231);

Also, you are mocking IDataService and calling it sut , sut would be the controller, a SUT usually isn't mocked or stubbed, again this is opinionated... :-)

Given that the service is defined as async, that action should be async as well

    [HttpGet("/value")]
    public async Task<IActionResult> GetTotal(string name, string year) {
        decimal totalAmount = await _dataService.GetToTalValue(name, year);
        return Ok(totalAmount);
    }

otherwise keep everything synchronous

For unit testing, you are verifying the expected behavior of the subject under test in isolation.

Again since the subject is async, then the test should be async as well

public class UnitTest1 {
    [Fact]
    public async Task Test1() {
        //Arrange
        string name = "ASK";
        string year = "1235";
        decimal expected = 1231;

        //setup expectations of the mocked dependency
        var mock = new Mock<IDataService>();    
        mock.Setup(_ => _.GetTotalValue(name, year)).ReturnsAsync();

        HomeController sut = new HomeController(mock.Object);

        //Act
        IActionResult result = await sut.GetTotal(name, year);
        //Need to convert to actual action result Type
        OkObjectResult okResult = result as OkObjectResult;

        //Assert
        okResult.Should().NotBeNull();
        //need to extract the actual value within the result
        decimal actual = (decimal)okResult.Value;
        //verify expected behavior
        actual.Should().Be(expected); //using FluentAssertions.
    }
}

This has been simplified because of the simple nature of the example given, but the end goal here is to verify the expected behavior of the subject under test by giving it known values and asserting expected results.

Since the subject under test is the controller in this case, then the implementation of the actual interface is of no concern for the purposes of this isolated test. Mock the desired behavior and give that mocked dependency to the controller (the subject).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM