Is it a good practice to mock Automapper in unit tests?
Asked Answered
D

2

11

There is this codebase where we use automapper and have 2 layers, Domain and Service. Each has its object for data representation, DomainItem and ServiceItem. The service gets data from domain, the uses constructor injected automapper instance to map

class Service 
{
  public ServiceItem Get(int id)
  {
    var domainItem = this.domain.Get(id);
    return this.mapper.Map<DomainItem, ServiceItem>(domainItem);
  }
}

Assume best practices, so mapper has no side-effects and no external dependencies. You'd write a static function to convert one object to another within seconds, just mapping fields.

With this in mind, is it a good practice to mock the mapper in unit tests like this?

[TestClass]
class UnitTests
{
  [TestMethod]
  public void Test()
  {
    var expected = new ServiceItem();

    var mockDomain = new Mock<IDomain>();
    // ... setup
    var mockMapper = new Mock<IMapper>();
    mockMapper.Setup(x => x.Map<DomainItem, ServiceItem>(It.IsAny<DomainItem>()))
      .Returns(expected);


    var service = new Service(mockDomain.Object, mockMapper.Object);
    var result = service.Get(0);

    Assert.AreEqual(expected, result);
  }
}

To me, it seems that such unit test does not really bring any value, because it is effectively testing only the mocks, So i'd either not write it at all OR I'd use the actual mapper, not the mocked one. Am I right or do I overlook something?

Demote answered 12/10, 2016 at 12:2 Comment(3)
Contrary to popular opinion, anytime you mock functionality during testing, you give yourself a false sense of securityTaste
Well this depends on the get method If the method looks like this Get(int id) => mapper.Map(db.Get(id)) ; Not Really. If it has some other logic it can be, like exceptions. DI as a pattern has this Mock test problem as a whole. When they say it's easy for unite tests the "easy" part is when you test things that don't really need to be tested.Ignatia
@MickyD: I can understand that, mostly when I am forced to write such tests, I feel very... unsafe.Demote
J
18

I think the issue here is that the test is badly written for what it is actually trying to achieve which is testing Service.Get().

The way I would write this test is as follows:

[TestMethod]
public void Test()
{
  var expected = new ServiceItem();

  var mockDomain = new Mock<IDomain>();
  var expectedDomainReturn = new DomainItem(0); //Illustrative purposes only
  mockDomain.Setup(x => x.DomainCall(0)).Returns(expectedDomainReturn); //Illustrative purposes only

  var mockMapper = new Mock<IMapper>();
  mockMapper.Setup(x => x.Map<DomainItem, ServiceItem>(It.IsAny<DomainItem>()))
      .Returns(expected);    


  var service = new Service(mockDomain.Object, mockMapper.Object);
  var result = service.Get(0);

  mockDomain.Verify(x => x.DomainCall(0), Times.Once);
  mockMapper.Verify(x => x.Map<DomainItem, ServiceItem>(expectedDomainReturn), Times.Once);
}

This test instead of not really checking the functionality of the service.Get(), checks that the parameters passed are correct for the individual dependency calls based on the responses. You are thus not testing AutoMapper itself and should not need to.

Checking result is basically useless but will get the code coverage up.

Jewry answered 12/10, 2016 at 12:23 Comment(4)
So, if I understand it correctly, such unit test makes sure that dependencies are wired correctly inside the method it tests? Meaning, the test verifies the implementation?Demote
Correct - a more complex method which performs further transformations on dependency parameters would test those transformations during the method invocation verification process.Jewry
Okay, thank you. Although I still don't like the test itself much, at least I understand it's purpose now. :)Demote
Just stumbled across this. The verifications are superfluous. If mockMapper was set up with .Setup(x => x.Map<DomainItem, ServiceItem(expectedDomainReturn)).Returns(expected); then a simple Assert.AreSame(expected, result); would suffice, because there's no way for it to pass without both the domain call and the mapper being called.Counterpoint
B
1

I know that I'm late but I think that is not a good idea mock Automapper, because you could forgot to map a class or some other mappings errors.

If we add a class with a Profile for example

public Profile()
{
    CreateMap<MyClass1, MyClass2>()
        .ForMember((MyClass2 dest) => dest.Id, (opt) => opt.MapFrom(src => src.Id))
    ...
}

You can create your mapper in your test

var myProfile = new Profile();
var configuration = new MapperConfiguration(cfg => 
    cfg.AddProfile(myProfile));
IMapper mapper = new Mapper(configuration);

You can test if there are some problems with your mapping

mapper.ConfigurationProvider.AssertConfigurationIsValid();

This occur if you don't set a trasformation for one property or if you define the same trasformation more than one time.

So you can test your Automapper Configuration.

About this last property see also

AssertConfigurationIsValid and Automapper documentation

Baer answered 22/8, 2023 at 13:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.