0

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.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
Bharat
  • 5,869
  • 4
  • 38
  • 58
  • 1
    I didn't downvote, but out of curiosity, why you would mock automapper? Automapper is not part of external(slow) resources it is not part of different domain - it is implementation details which you probably going to to remove later :) – Fabio Jul 19 '21 at 04:05
  • @Fabio Good question. here is a thread on that which I asked community. https://stackoverflow.com/questions/49934707/automapper-in-xunit-testing-and-net-core-2-0/49940581#49940581 – Bharat Jul 19 '21 at 04:50
  • 1
    @Bharat Are you using the AutoMapper via the `IMapper` interface in your controller? – Peter Csala Jul 19 '21 at 07:41
  • yes @PeterCsala – Bharat Jul 19 '21 at 08:11

1 Answers1

1

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))
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
  • Let me try this solution. – Bharat Jul 19 '21 at 11:14
  • @Bharat Could you please a bit more specific? Are you facing a compile time or runtime issue? What is the error message? – Peter Csala Jul 19 '21 at 11:33
  • both, I fixed some compile issue but now I'm not able to map the model properly – Bharat Jul 19 '21 at 11:35
  • @Bharat What does it mean *"I'm not able to map the model properly"*? Does the `Map` call fail? Is it called anyway? Does the verification fail? – Peter Csala Jul 19 '21 at 11:38
  • I mean using mapperMock, I'm not able to map my entity with dto. it returns null for `mapper.Map(source: addedSurveyRequest);` which I converted to `var model = mapperMock.Object.Map(source: addedSurveyRequest);` – Bharat Jul 19 '21 at 11:40
  • 1
    @Bharat In the `Setup` call, you should not specify the parameter of `Map`. The `mapperMock.Object` should be passed to the `SurveyRequestController`. – Peter Csala Jul 19 '21 at 11:44
  • 1
    @Bharat So, this line: `SurveyRequestController controller = new SurveyRequestController(repositories: mockRepo.Object, mapper: mapper);` should be changed to `SurveyRequestController controller = new SurveyRequestController(repositories: mockRepo.Object, mapper: mapperMock.Object);` – Peter Csala Jul 19 '21 at 11:45
  • still same issue, response remains null. – Bharat Jul 19 '21 at 11:53
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/235056/discussion-between-peter-csala-and-bharat). – Peter Csala Jul 19 '21 at 11:58