Search code examples
c#automappermoq.net-5xunit

xUnit and .Net core post test case issue with automapper(I guess)


I'm working on CRUD unit test cases with having configuration .Net 5, Automapper, xUnit etc.

The issue:

  • So right now I'm having issue specifically in post call and when I uses Dto.
  • I tried lots of ways to resole it and I was also able to resolve it if I don't use Dto in post call as input parameter and just use Entity it self. (But I don't want to expose entity so I want to make it working with Dto only)
  • Below is test which is not working and it's controller side implementation.

Failing Test case:

[Fact]
public void PostTest()
{
    try
    {
        //Arrange
        var surveyRequest = new SurveyRequest()
        {
            Id = 0,
            Name = "Survey Request 1",
            CreateDate = DateTime.Now,
            CreatedBy = 1
        };
        var addedSurveyRequest = new SurveyRequest()
        {
            Id = 1,
            Name = "Survey Request 1",
            CreateDate = DateTime.Now,
            CreatedBy = 1
        };

        //setup mock
        Mock<IRepositoryWrapper> mockRepo = new Mock<IRepositoryWrapper>();
        mockRepo.Setup(m => m.SurveyRequest.Add(addedSurveyRequest)).Returns(new Response<SurveyRequest>(true, addedSurveyRequest));

        //auto mapper
        var mockMapper = new MapperConfiguration(cfg =>
        {
            cfg.AddProfile(new AutoMapperProfile());
        });
        var mapper = mockMapper.CreateMapper();

        SurveyRequestController controller = new SurveyRequestController(repositories: mockRepo.Object, mapper: mapper);

        //Act
        var model = mapper.Map<SurveyRequest, SurveyRequestDto>(source: addedSurveyRequest);
        var result = controller.Post(model); // The issue with this post call here is that response remains null on repository level.

        //Assert
        var okResult = result as OkObjectResult;
        Assert.NotNull(okResult);

        //we will make sure that returned object is dto and not actual entity
        var response = okResult.Value as SurveyRequestDtoWithId;
        Assert.NotNull(response);

        Assert.Equal(expected: response.Name, actual: model.Name);
    }
    catch (Exception ex)
    {
        //Assert                
        Assert.False(true, ex.Message);
    }
}

Controller side post call:

[HttpPost("Insert")]
public IActionResult Post([FromBody] SurveyRequestDto model)
{
    try
    {
        if (!ModelState.IsValid)
            return BadRequest(ModelState);

        //If I remove this mapping from here, test case will work. (see next working test case)
        var entity = _mapper.Map<SurveyRequestDto, SurveyRequest>(source: model);
        entity.CreateDate = System.DateTime.Now;
        entity.CreatedBy = 1;

        var response = _repositories.SurveyRequest.Add(entity: entity); //Response remains null here
        _repositories.Save();

        if (response.IsSuccess == true)
            return new OkObjectResult(_mapper.Map<SurveyRequest, SurveyRequestDtoWithId>(source: response.Data));
        else
            return new ObjectResult(response.ErrorMessage) { StatusCode = 500 };
    }
    catch (Exception ex)
    {
        return new ObjectResult(ex.Message) { StatusCode = 500 };
    }            
}

Working Test case:

[Fact]
public void PostTest2()
{
    try
    {
        //Arrange
        var surveyRequest = new SurveyRequest()
        {
            Id = 0,
            Name = "Survey Request 1",
            CreateDate = DateTime.Now,
            CreatedBy = 1
        };
        var addedSurveyRequest = new SurveyRequest()
        {
            Id = 1,
            Name = "Survey Request 1",
            CreateDate = DateTime.Now,
            CreatedBy = 1
        };

        //setup mock
        Mock<IRepositoryWrapper> mockRepo = new Mock<IRepositoryWrapper>();
        mockRepo.Setup(m => m.SurveyRequest.Add(surveyRequest)).Returns(value: new Response<SurveyRequest>(true, addedSurveyRequest));

        //auto mapper
        var mockMapper = new MapperConfiguration(cfg =>
        {
            cfg.AddProfile(new AutoMapperProfile());
        });
        var mapper = mockMapper.CreateMapper();

        //setup controlller
        SurveyRequestController controller = new SurveyRequestController(repositories: mockRepo.Object, mapper: mapper);

        //Act
        //var model = mapper.Map<SurveyRequest, SurveyRequestDto>(source: surveyRequest);
        var result = controller.Post2(entity: surveyRequest);

        //Assert
        var okResult = result as OkObjectResult;
        Assert.NotNull(okResult);

        ///we will make sure that returned object is dto and not actual entity
        var response = okResult.Value as SurveyRequestDtoWithId;
        Assert.NotNull(response);

        Assert.Equal(expected: response.Id, actual: addedSurveyRequest.Id);
        Assert.Equal(expected: response.Name, actual: addedSurveyRequest.Name);
    }
    catch (Exception ex)
    {
        //Assert                
        Assert.False(true, ex.Message);
    }
}

Controller side Post call for working test case:

[HttpPost("Insert")]
public IActionResult Post2([FromBody] SurveyRequest entity)
{
    try
    {
        if (!ModelState.IsValid)
            return BadRequest(ModelState);

        //var entity = _mapper.Map<SurveyRequestDto, SurveyRequest>(source: model);
        //entity.CreateDate = System.DateTime.Now;
        //entity.CreatedBy = 1;

        var response = _repositories.SurveyRequest.Add(entity: entity); //This returns proper response with saved data and ID
        _repositories.Save();

        if (response.IsSuccess == true)
            return new OkObjectResult(_mapper.Map<SurveyRequest, SurveyRequestDtoWithId>(source: response.Data));
        else
            return new ObjectResult(response.ErrorMessage) { StatusCode = 500 };
    }
    catch (Exception ex)
    {
        return new ObjectResult(ex.Message) { StatusCode = 500 };
    }
}

I'm not sure whether my test case setup for mapper is wrong or any other issue. I also tried lots of ways but no luck so far. So posting here if someone can look and help, will be much appreciated.


Solution

  • If you are using IMapper then you can do something like this:

    var mapperMock = new Mock<IMapper>();
    mapperMock
       .Setup(mapper => mapper.Map<SurveyRequestDto, SurveyRequest>(It.IsAny< SurveyRequestDto>()))
       .Returns(surveyRequest);
    

    This solution does not utilize the AutoMapperProfile, but because you have only a single mapping that's why I think it not really a problem.

    If you want to call Verify on the mapperMock then I would suggest to extract the Map selector delegate like this:

    private static Expression<Func<IMapper, SurveyRequestDto>> MapServiceModelFromRequestModelIsAny =>
       mapper => mapper.Map<SurveyRequestDto, SurveyRequest>(It.IsAny< SurveyRequestDto>());
    

    Usage

    //Arrange
    mapperMock
       .Setup(MapServiceModelFromRequestModelIsAny)
       .Returns(surveyRequest);
    
    ...
    
    //Assert
    mapperMock
       .Verify(MapServiceModelFromRequestModelIsAny, Times.Once);
    

    UPDATE #1

    It might also make sense to be as explicit as possible when you make assertion. If you want to you can do deep equality check to make sure that controller's parameter is not amended before the Map call:

    private static Expression<Func<IMapper, SurveyRequestDto>> MapServiceModelFromRequestModel(SurveyRequestDto input) =>
       mapper => mapper.Map<SurveyRequestDto, SurveyRequest>(It.Is< SurveyRequestDto>(dto => dto.deepEquals(input)));
    
    
    //Assert
    mapperMock
       .Verify(MapServiceModelFromRequestModel(model), Times.Once);
    

    This assumes that deepEquals is available as an extension method.


    UPDATE #2

    As it turned out the mock repository's Setup code also had some problem. Namely it used the surveyRequest rather than a It.IsAny<SurveyRequest>().

    Because surveyRequest was specified as the expected parameter that's why the setupped code path is never called but returned with null.

    After changed it to It.IsAny then the whole test started to work :D

    repoMock
       .Setup(repo => repo.SurveyRequest.Add(It.IsAny<SurveyRequest>()))
       .Returns(new Response<SurveyRequest>(true, addedSurveyRequest))