【问题标题】:How to mock mapping profile with mapping options如何使用映射选项模拟映射配置文件
【发布时间】:2020-05-01 02:39:59
【问题描述】:

我有一个使用IDictionary<string, object> 传递值的映射配置文件。我被这样使用:

var viewModel = _mapper.Map<TransactionDetailsViewModel>(
    transaction,
    opt =>
    {
        opt.Items.Add( typeof(IClockService).Name, _systemClock );
    }
);

我尝试在单元测试中模拟它,但总是在这里得到NotSupportedException

Mock<IMapper> _mockMapper = new Mock<IMapper>();
_mockMapper
    .Setup(
        mm => mm.Map<TransactionDetailsViewModel>(
            domainModel,
            opt => opt.Items.Add( typeof(IClockService).Name, _systemClock )
        )
    )
    .Returns( viewModel );

模拟这种映射方法的正确方法是什么?

这里是StackTrace

 at Moq.MatcherFactory.CreateMatcher(Expression expression)
   at Moq.MatcherFactory.CreateMatcher(Expression argument, ParameterInfo parameter)
   at Moq.MatcherFactory.CreateMatchers(IReadOnlyList`1 arguments, ParameterInfo[] parameters)
   at Moq.InvocationShape..ctor(LambdaExpression expression, MethodInfo method, IReadOnlyList`1 arguments, Boolean exactGenericTypeArguments)
   at Moq.ExpressionExtensions.<Split>g__Split|4_1(Expression e, Expression& r, InvocationShape& p)
   at Moq.ExpressionExtensions.Split(LambdaExpression expression)
   at Moq.Mock.Setup(Mock mock, LambdaExpression expression, Condition condition)
   at Moq.Mock`1.Setup[TResult](Expression`1 expression)
   at Hosts.Api.Tests.TransactionsControllerTests.GetTransaction_WhenAdvance_ConvertsSpread() in C:\Users\RS\source\repos\Project\src\UnitTests\Hosts.Api.Tests\Operations\Transactions\TransactionsControllerTests.cs:line 211

消息:

Unsupported expression: opt => 
opt.Items.Add(Services.ClockService.Interfaces.IClockService.Name,
value(Hosts.Api.Tests.TransactionsControllerTests)._systemClock)

【问题讨论】:

  • 我建议您不要模拟IMapper:它在所有情况下的行为都应该相同:(单元测试、集成测试和正常运行)- AutoMapper 无论如何都支持 DI。你有什么理由想在你的测试中模拟它吗? (您总是可以配置 AutoMapper 以不同方式进行测试,但这与模拟它不同)。
  • @Dai 我同意更好的选择是在测试服务中注入真实版本的映射器。在当前的单元测试以及其他类中,映射器被嘲笑。我不想在更改测试服务的实现并向映射配置文件添加小的更改后更新所有单元测试。只是在我试图模拟选项的方式中徘徊到底是不正确的。没有选项的版本按预期工作。
  • NotSupportedException 究竟是从哪里抛出的? (即StackTrace 的顶行是什么?)domainModeltransaction 相比如何?
  • 顺便说一句,考虑使用nameof(IClockService) 而不是typeof(IClockService).Namenameof() 不适用于泛型类型参数,ofc)。
  • 谢谢。我将会。这是 StackTrace:at Moq.MatcherFactory.CreateMatcher(Expression expression) at Moq.MatcherFactory.CreateMatcher(Expression argument, ParameterInfo parameter)at Moq.MatcherFactory.CreateMatchers(IReadOnlyList1 个参数,ParameterInfo[] parameters) at Moq.InvocationShape..ctor(LambdaExpression expression, MethodInfo method, IReadOnlyList1 个参数,布尔精确通用类型参数)在 Moq.ExpressionExtensions.g__Split|4_1(Expression e, Expression& r, InvocationShape& p) ...

标签: c# .net-core automapper moq


【解决方案1】:

通常我不会模拟映射器;我认为大多数时候集成测试映射配置文件是一件好事,但我会根据需要不时进行。

从为选项指定匹配器开始:

mapperMock.Setup(x => x.Map<TransactionDetailsViewModel>(
        transaction,
        It.Is<Action<IMappingOperationOptions>>(opt => AddsItem(opt, nameof(IClockService), systemClock)))).Returns(viewModel);

您无法匹配委托本身,但在这种情况下,您可以匹配它正在执行的操作 - 将项目添加到项目字典。我已将实际比较委托给方法 AddsItem 以保持代码可读性。

public bool AddsItem(Action<IMappingOperationOptions> providedOptions, string key, object value)
{
    var mappingOptions = new CustomMappingOperationOptions();
    providedOptions.Invoke(mappingOptions);
    return mappingOptions.Items.Any(x => x.Key.Equals(key) && x.Value.Equals(value));
}

基本上,比较解决了委托正在做什么,并根据它是否符合我们的预期返回真或假。切碎并更改比较以适合,我在这里做出假设。 CustomMappingOperationOptions 只是 IMappingOperationOptions 的快速本地实现,因为具体实现看起来有点痛苦,我们不需要它来进行测试。

最后,把它们放在一起:

var mapperMock = new Mock<IMapper>();
mapperMock.Setup(x => x.Map<TransactionDetailsViewModel>(
        transaction,
        It.Is<Action<IMappingOperationOptions>>(opt => AddsItem(opt, nameof(IClockService), new ClockService())))).Returns(viewModel);

var mockedMapper = mapperMock.Object;

var result = mockedMapper.Map<TransactionDetailsViewModel>(transaction, opt => opt.Items.Add(nameof(IClockService), new ClockService()));

【讨论】:

  • 谢谢 rgvlee。我仍然最终使用了真正的映射配置文件,但是我正在寻找这个解决方案并且它有效。我试着像这样在Additems() 内部嘲笑IMappingOperationOptionspublic bool AddItems(Action&lt;IMappingOperationOptions&gt; opt, string key, object value) { var mapOptions = new Mock&lt;IMappingOperationOptions&gt;(); mapOptions.Setup(x =&gt; x.Items).Returns(new StringDictionary()); opt.Invoke(mapOptions.Object); return mapOptions.Object.Items.Any(x =&gt; x.Key.Equals(key) &amp;&amp; x.Value.Equals(value)); }
  • 嗨 Roman,干得好,带有新字典的 Returns 在这种情况下效果很好。
猜你喜欢
  • 1970-01-01
  • 2020-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-01
相关资源
最近更新 更多