在单元测试中模拟Automapper是一种好的实践吗?

11

我们有一个代码库,使用automapper技术,分为两层,DomainService。每个层都有自己的对象来表示数据,DomainItemServiceItem。服务层从域层获取数据,然后使用构造函数注入的automapper实例进行映射。

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

假设最佳实践,使映射器没有副作用和外部依赖。您可以编写一个静态函数,在几秒钟内将一个对象转换为另一个对象,只需映射字段即可。

考虑到这一点,像这样在单元测试中模拟映射器是一种好的做法吗?

[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);
  }
}

对我来说,这种单元测试似乎并没有带来任何价值,因为它实际上只测试了mock对象。所以我要么不写它,要么使用真正的映射器而不是mock的那个。我是正确的还是有什么我忽略了的东西?


4
与普遍观点相反,任何时候在测试过程中嘲笑功能,都会给自己带来一种错误的安全感。 - user585968
1
这取决于get方法。如果方法看起来像这样 Get(int id) => mapper.Map(db.Get(id)) ; 那么不是很难。但如果它有其他逻辑,比如异常处理,那就可能会比较困难。依赖注入作为一种模式,整体上存在着Mock测试问题。当他们说它对单元测试很容易时,“容易”部分是指你测试的东西并不需要真正被测试。 - Filip Cordas
@MickyD:我能理解,大多数时候当我被迫编写这样的测试时,我会感到非常不安。 - netchkin
2个回答

16

我认为这里的问题在于该测试的编写质量较差,因为它实际上想要测试的是Service.Get()

我会按如下方式编写这个测试:

[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);
}

这个测试并没有真正检查service.Get()函数的功能,而是检查传递的参数是否基于响应正确地调用了各个依赖项。因此,您不需要对AutoMapper进行测试。

检查result基本上是无用的,但会提高代码覆盖率。


所以,如果我理解正确的话,这种单元测试确保在测试的方法内部正确连接依赖项?也就是说,测试验证了实现? - netchkin
更复杂的方法会在依赖参数上执行进一步的转换,并在方法调用验证过程中测试这些转换。 - toadflakz
好的,谢谢。虽然我仍然不太喜欢这个测试本身,但至少现在我明白它的目的了。 :) - netchkin
刚刚偶然发现了这个。 这些验证是多余的。如果mockMapper被设置为.Setup(x => x.Map<DomainItem, ServiceItem(expectedDomainReturn)).Returns(expected);,那么一个简单的Assert.AreSame(expected, result);就足够了,因为除非同时调用了域和映射器,否则它不可能通过。 - Andras Zoltan

1
我知道我迟到了,但我认为嘲笑Automapper不是一个好主意,因为你可能会忘记映射一个类或其他映射错误。
如果我们添加一个带有Profile的类作为示例
public Profile()
{
    CreateMap<MyClass1, MyClass2>()
        .ForMember((MyClass2 dest) => dest.Id, (opt) => opt.MapFrom(src => src.Id))
    ...
}

你可以在你的测试中创建你的映射器。
var myProfile = new Profile();
var configuration = new MapperConfiguration(cfg => 
    cfg.AddProfile(myProfile));
IMapper mapper = new Mapper(configuration);

你可以测试一下你的映射是否有问题。
mapper.ConfigurationProvider.AssertConfigurationIsValid();

如果您没有为某个属性设置转换,或者定义了多次相同的转换,则会发生这种情况。
所以您可以测试您的Automapper配置。
关于最后一个属性,请参阅 AssertConfigurationIsValidAutomapper文档

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接