1

So I am very new to writing tests. I created an ASP.NET core web api along with angular. I have to write unit tests for the web API controllers. I have been reading Microsoft documentation on how to get started with unit tests of ASP.NET web APIs. But I am still very unsure on how to go about writing proper tests.

My Controller Code

[Authorize]
[Route("api/[controller]")]
[ApiController]
public class OrdersController : ControllerBase
{
    private readonly IOrderRepository _repo;
    private readonly IMapper _mapper;
    public OrdersController(IOrderRepository repo, IMapper mapper)
    {
        _repo = repo;
        _mapper = mapper;
    }

    [AllowAnonymous]
    [HttpPost()]
    public async Task<IActionResult> AddOrder(OrderForMappingDto orderForMappingDto)
    {
        //if(orderForMappingDto.ARentalOrNot == null)
        //{
        //    throw new Exception("Value can't be left null");
        //}
        var orderToCreate = _mapper.Map<TblOrder>(orderForMappingDto);
        var createdOrder = await _repo.AddOrder(orderToCreate);

        return Ok(createdOrder);
    }

}

My Repository Code

public class OrderRepository : IOrderRepository
{
    private readonly MovieRentalDBContext _context;

    public OrderRepository(MovieRentalDBContext context)
    {
        _context = context;
    }
    public async Task<TblOrder> AddOrder(TblOrder tblOrder)
    {
        await _context.TblOrder.AddAsync(tblOrder);
        await _context.SaveChangesAsync();
        return tblOrder;
    }
}

I understand there is a lot of mocking to be done. But do I need to mock the Entity Framework as well? I wrote a simple test file.

public void PostsAorder_WhenCalled_ReturnsOkWithResponse()
{
    var mockOrderRepository = new Mock<IOrderRepository>();
    var mockOrderMapper = new Mock<IMapper>();
    var orderControllerObject = new OrdersController(mockOrderRepository.Object, mockOrderMapper.Object);

    Task<IActionResult> contentResult = orderControllerObject.AddOrder(new OrderForMappingDto
    {
        ACustomerId = 3,
        AMovieId = 18,
        ARentalOrNot = false,
        AOrderedDate = DateTime.Now
    }) ;
    //var contentResult = actionResult as OkNegotiatedContentResult<OrderForMappingDto>;
    Assert.IsNotNull(contentResult);
    Assert.IsNotNull(contentResult.Result);
}

The OkNegotioatedContent function doesn't work with Tasks. How do I go about using that for task. Also, the tests is passing even when I don't supply the last 3 arguments even though in the DTO they are classified as [Required]. Can somebody help on how to modify the test properly.

mapper Configuration-

Mapper returning null

4
  • You don't need to mock the entire Entity Framework. IMO you could use the existing dev database and delete all your objects that were created during testing after the test run completed. But if you don't want to do this, you can use EF core's in in memory database which exists entirely in-memory and everything gets deleted after the fact Commented Oct 9, 2020 at 11:01
  • Also, IMO, you don't need to mock your repo and mapper as well, use the real thing. What good does it do for you to know if your controller method works with mocked data? What you really want to know is if your controller method works with real data Commented Oct 9, 2020 at 11:16
  • @MindSwipe If i use the real thing, wouldn't that make it an integration test with them being external dependences and all? Commented Oct 9, 2020 at 11:24
  • 1
    Well, it makes the test (again, IMO) more valuable. Right now the only thing your testing is that you controller does not return null, not if actually does what it's supposed to (apply validation, map, and save). I've honestly never cared about the classifications of unit tests, just made sure that all of the code I write is properly tested, meaning at least one positive test and one negative test which both cover all edge cases I can think of Commented Oct 9, 2020 at 11:28

1 Answer 1

4

Your test is almost okay. First the fixed version then some explanation:

[Fact]
public async Task GivenAValidOrder_WhenICallTheAsOrder_ThenItReturnsOkWithResponse()
{
    //Arrange
    var mockOrderMapper = new Mock<IMapper>();
    mockOrderMapper.Setup(mapper => mapper.Map<TblOrder>(It.IsAny<OrderForMappingDto>()))
        .Returns(new TblOrder());

    var mockOrderRepository = new Mock<IOrderRepository>();
    mockOrderRepository.Setup(repo => repo.AddOrder(It.IsAny<TblOrder>()))
        .ReturnsAsync((TblOrder order) => order);

    var SUT = new OrdersController(mockOrderRepository.Object, mockOrderMapper.Object);

    //Act
    var contentResult = await SUT.AddOrder(new OrderForMappingDto
    {
        ACustomerId = 3,
        AMovieId = 18,
        ARentalOrNot = false,
        AOrderedDate = DateTime.Now
    });

    //Assert
    Assert.NotNull(contentResult);
    Assert.IsAssignableFrom<OkObjectResult>(contentResult);

    var result = ((OkObjectResult)contentResult).Value;
    Assert.NotNull(result);
    Assert.IsAssignableFrom<TblOrder>(result);
}
  1. The name of test follows the Given When Then structure to make it easier to understand that under what circumstances how should the controller's action behave.
  2. The test is now asynchronous because we need to await the controller's action to finish in order to examine its result.
  3. I've added the Arrange Act Assert comments to the code in order to emphasize which phase starts when.
  4. I've set the mapper mock to return a new TblOrder and the repo mock to return whatever it receives.
  5. I've renamed orderControllerObject to SUT, because it emphasize which component is under examination (System Under Test).
  6. In the result verification I've used IsAssingableForm instead of IsType because it checks against derived classes as well. In this particular case it is not mandatory, but it is a good practice.
  7. The result type will be OkObjectResult not OkNegotiatedContentResult, so you should check against that.
  8. And finally I've added an extra check to make sure that the returned object's type is that what is expected.
Sign up to request clarification or add additional context in comments.

10 Comments

Hey, Thanks for the answer but getting two errors and I tried making changes but the error would just change. 1)Cannot implicitly convert type 'MovieRentalApp.Models.TblOrder' to 'System.Threading.Tasks.Task<MovieRentalApp.Models.TblOrder>' MovieRentalApp.UnitTests 2) Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type
Thanks gor for letting me know. Tomorrow I will fix it.
@TusharGoel I've updated and tested the code. Apologize for the inconvenience.
Thanks for all the help
Hi Peter, so I tried your solution and it fixed a lot of bits but I am having an issue with the mock mapper setup . Because I found that in debugging, in the addOrder funcation, it goes inside the controller and the mapping function returns a null object. How do i fix the mapping setup. var orderToCreate = _mapper.Map<TblOrder>(orderForMappingDto); returns null
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.