Search code examples
c#asp.net-core-webapimoqxunit

Mocked Interface returning NULL


I've read similar questions but could not manage to solve my own. I'm using xUnit and I'm running into an issue when one of my methods calls, it's returning null but in fact that I have mocked it.

Interface

public interface IApplicantService
{
    Task<Applicant> AddAsync(Applicant applicant);
    // other methods
}

Test Case

public class ApplicationControllerTests
{
    private readonly Mock<IApplicantService> _mockApplicantService;
    private readonly ApplicantController _applicantController;
    private readonly IMapper _mockMapper;

    public ApplicationControllerTests()
    {
        _mockApplicantService = new Mock<IApplicantService>();

        var mapperConfig = new MapperConfiguration(cfg =>
        {
            cfg.AddProfile(new ResourceToModelProfile());
            cfg.AddProfile(new ModelToResourceProfile());
        });
        _mockMapper = mapperConfig.CreateMapper();

        _applicantController = new ApplicantController(_mockApplicantService.Object, _mockMapper);
    }

    [Fact]
    public async void CreateAsync_WhenApplicantNotExist_ShouldReturn_CreatedAtActionResult_With_Resource()
    {
        var applicantDto = new ApplicantCreateDto
        {
            PersonId = 1,
            VacancyId = 1
        };

        _mockApplicantService.Setup(e => e.AddAsync(It.IsAny<Applicant>()))
                            .Returns(Task.FromResult(new Applicant { Id = 1, PersonId = 1, VacancyId = 1}));

        var result = await _applicantController.CreateAsync(applicantDto);

        var createdAtActionResult = result as CreatedAtActionResult;

        var model = createdAtActionResult.Value as ApplicantResponseDto;

        var actual = model.PersonId;

        Assert.NotNull(model);            
        Assert.Equal(1, actual);
        Assert.NotNull(createdAtActionResult);
    }
}

Controller

[HttpPost]
[Route("CreateAsync")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> CreateAsync([FromBody] ApplicantCreateDto applicantCreateDto)
{
    try
    {
        var applicant = _mapper.Map<ApplicantCreateDto, Applicant>(applicantCreateDto);

        var result = await _applicantService.AddAsync(applicant);
        // here, result is null, but it was mocked to return an Applicant object

        var resource = _mapper.Map<Applicant, ApplicantResponseDto>(result);

        return CreatedAtAction(nameof(GetAsync), new { id = result.Id }, resource);
    }
    catch (ResourceExistException ex)
    {
        return Conflict(ex.Message);
    }
    catch(Exception ex)
    {   
        // log exception             
        return StatusCode(500);
    }
}

The mocked method is returning null and I'm getting System.NullReferenceException


Solution

  • This is how your fixed unit test could look like:

    [Fact]
    public async Task CreateAsync_WhenApplicantNotExist_ShouldReturn_CreatedAtActionResult_With_Resource()
    {
        //Arrange
        var applicantDto = new ApplicantCreateDto { PersonId = 1, VacancyId = 1 };
        var applicant = new Applicant { Id = 1, PersonId = 1, VacancyId = 1 };
    
        _mockApplicantService
            .Setup(svc => svc.AddAsync(It.IsAny<Applicant>()))
            .ReturnsAsync(applicant);
    
        //Act
        var result = await _applicantController.CreateAsync(applicantDto);
    
        //Assert
        var createdAtActionResult = Assert.IsAssignableFrom<CreatedAtActionResult>(result);
        var model = Assert.IsAssignableFrom<ApplicationResponseDto>(createdAtActionResult.Value);
        Assert.Equal(1, model.PersonId);
    }
    
    • I've replaced async void to async Task that way your await will be evaluated properly
    • I've changed the Returns(Task.FromResult(...)) to ReturnsAsync(...) because this is the recommended way to specify return value in case of async methods
    • I've also added some comments to separate the different phases of your unit test from each other (Arrange-Act-Assert)
    • I've changed your assertion logic to use IsAssingableFrom to verify the type itself rather than doing null checks