Search code examples
c#automappermoqxunit

How to use a Mock in xunit passing a mapped entity


I'm trying to create an unit test using moq on xunit, but the test is failing, I guest is because of the mapper, when the _sut.Create is called it's thrown an exception, if I remove MockBehavior.Strict behavior _sut.Create returns 0 instead of 1.

How can I fix this?, Thanks for your ideas.

_logger = new Mock<ILogger<BaseCRUDDomainService<ClientDTO, client>>>().Object;

var mockMapper = new MapperConfiguration(cfg =>
{
    cfg.AddProfile(new DomainMapper());
});
_mapper = mockMapper.CreateMapper();

var mockRepository = new Mock<IBaseCRUDRepository<client>>(MockBehavior.Strict);
//reponse create 1
var clientDTO = new ClientDTO
{
    Name = "name",
    Description = "description",
    Address = "address",
    Billing = "billing",
    CellphoneNumber = "cellphone",
    Email = "email"
};
mockRepository.Setup(r => r.Create(_mapper.Map<client>(clientDTO)))
    .Returns(1);
var _baseCRUDRepository = mockRepository.Object;
var _sut = new ClientCRUDDomainService(_logger, _mapper, _baseCRUDRepository);
var response = _sut.Create(clientDTO);

This is the error throwed Moq.MockException: 'IBaseCRUDRepository<client>.Create(client) invocation failed with mock behavior Strict. All invocations on the mock must have a corresponding setup.'

This is the method that I want to test:

public int Create(clientDTO entity)
{
    var entityDB = _mapper.Map<clientDB>(entity);
    return _baseCRUDRepository.Create(entityDB);
}

This is the DTO class

public class ClientDTO
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public string Billing { get; set; }
    public string Address { get; set; }
    public string Email { get; set; }
    public string CellphoneNumber { get; set; }
    public int[]? Users { get; set; }
}

This is the database class

public partial class client
{
    public int Id { get; set; }
    public string name { get; set; } = null!;
    public string? description { get; set; }
    public string? billing { get; set; }
    public string? address { get; set; }
    public string? email { get; set; }
    public string? cellphone_number { get; set; }
}

And this is the mapper:

public class DomainMapper : Profile
{
    public DomainMapper() : base()
    {
        var configuration = new MapperConfiguration(cfg =>
        {
            cfg.AllowNullCollections = true;
        });

        configuration.CompileMappings();
        configuration.AssertConfigurationIsValid();

        CreateMap<ClientDTO, client>()
            .ForMember(db => db.Id, opt => opt.MapFrom(dto => dto.Id))
            .ForMember(db => db.cellphone_number, opt => opt.MapFrom(dto => dto.CellphoneNumber))
            .ReverseMap()
            .ForMember(dto => dto.Id, opt => opt.MapFrom(src => src.Id))
            .ForMember(dto => dto.CellphoneNumber, opt => opt.MapFrom(src => src.cellphone_number));
    }
}

Solution

  • The problem is here:

    mockRepository.Setup(r => r.Create(_mapper.Map<client>(clientDTO)))
        .Returns(1);
    

    This setup means, that for every call of Create method with this exact argument 1 will be return. Since client is a class without equality override, it means the class with the same reference. Which obviously doesn't happen, because mapper returns new class every time.

    The solution would be to specify constrains for the argument using build-in It.Is:

    mockRepository.Setup(
      r => r.Create(
        It.Is<client>(
          c => \** your condition here **\
        )
      ) 
    )
    .Returns(1);
    

    or use this nuget which adds equivalency check from FluentAssertions to Moq:

    mockRepository.Setup(
      r => r.Create(
        Its.EquivalentTo(
          /** your reference class here **/
        )
      ) 
    )
    .Returns(1);
    

    I would also highly suggest to not use the real mapper in the test and mock it, since in this test you are testing logic in ClientCRUDDomainService, not a mapping. For the mapper itself you could write another test, where you test only the mapping. With mocked mapper the test would be trivial:

    var mapperMock = new Mock<IMapper>();
    var mockRepository = new Mock<IBaseCRUDRepository<client>>(MockBehavior.Strict);
    var clientDTO = new ClientDTO();
    var client = new client();
    
    
    mapperMock.Setup(x => x.Map<client>(clientDTO)).Returns(client);
    mockRepository.Setup(r => r.Create(client)).Returns(1);
    var _sut = new ClientCRUDDomainService(_logger, mapperMock.Object, mockRepository.Object);
    

    P.S. Also a little suggestion to use NullLogger instead of mocked one if you don't check the logging in the test:

    var _logger = NullLogger<BaseCRUDDomainService<ClientDTO, client>>.Instanse;