如何模拟自定义 ValueResolver 构造函数参数

问题描述 投票:0回答:2

我在 .NET Core 中有一个项目,我正在使用 AutoMapper 在我的类型之间进行映射。但我遇到了一个问题,我想模拟自定义值解析器的参数。我创建了虚拟示例来显示问题:

假设我有一个 ValueResolver,它需要访问数据库来解析给定的成员:

public class NumberProfile : Profile
{
    public NumberProfile()
    {
        CreateMap<NumberModel, NumberDto>()
            .ForMember(dest => dest.Number, opt => opt.MapFrom<NumberValueResolver>());
    }
}

public class NumberValueResolver : IValueResolver<NumberModel, NumberDto, int>
{
    private readonly IRepository _repository;

    public NumberValueResolver(IRepository repository)
    {
        _repository = repository;
    }

    public int Resolve(NumberModel source, NumberDto destination, int destMember, ResolutionContext context)
    {
        return _repository.GetNumber().Number + 3;
    }
}

让我们想象一下,Resolve 方法中有逻辑需要测试,我想模拟存储库以便能够测试它。 这是我在测试中尝试过的

var repositoryMock = new Mock<IRepository>();
repositoryMock.Setup(s => s.GetNumber()).Returns(new NumberModel { Number = 3 });

var mapper = new MapperConfiguration(mc =>
{
    mc.AddProfile<NumberProfile>();
    mc.ConstructServicesUsing(s => new NumberValueResolver(repositoryMock.Object));
});

var service = new Service(repositoryMock.Object, mapper.CreateMapper());

然后我调用服务上的一个方法来触发映射。 这个案例很好,可以工作并且测试通过。当我添加另一个 ValueResolver 时,问题就出现了。

public class NumberProfile : Profile
{
    public NumberProfile()
    {
        CreateMap<NumberModel, NumberDto>()
            .ForMember(dest => dest.Number, opt => opt.MapFrom<NumberValueResolver>())
            .ForMember(dest => dest.AnotherNumber, opt => opt.MapFrom<AnotherNumberValueResolver>());
    }
}

public class NumberValueResolver : IValueResolver<NumberModel, NumberDto, int>
{
    private readonly IRepository _repository;

    public NumberValueResolver(IRepository repository)
    {
        _repository = repository;
    }

    public int Resolve(NumberModel source, NumberDto destination, int destMember, ResolutionContext context)
    {
        return _repository.GetNumber().Number + 3;
    }
}

public class AnotherNumberValueResolver : IValueResolver<NumberModel, NumberDto, string>
{
    public int Resolve(NumberModel source, NumberDto destination, string destMember, ResolutionContext context)
    {
        return $"{source.Number} number(s)";
    }
}

然后我想我会在测试中这样做

var repositoryMock = new Mock<IRepository>();
repositoryMock.Setup(s => s.GetNumber()).Returns(new NumberModel { Number = 3 });

var mapper = new MapperConfiguration(mc =>
{
    mc.AddProfile<NumberProfile>();
    mc.ConstructServicesUsing(s => new NumberValueResolver(repositoryMock.Object));
    mc.ConstructServicesUsing(s => new AnotherNumberValueResolver());
});

var service = new Service(repositoryMock.Object, mapper.CreateMapper());

但是后来我得到了错误:

System.InvalidCastException:无法将“AutoMapper_app.ValueResolvers.AnotherNumberValueResolver”类型的对象强制转换为“AutoMapper_app.ValueResolvers.NumberValueResolver”类型。

我是否误解了

ConstructServicesUsing
或 ValueResolvers 的用法,还是完全不同?我错过了一些非常明显的东西吗?难道是设计不好?

c# unit-testing .net-core automapper moq
2个回答
5
投票

有同样的问题。在研究了预期的结果之后,函数委托会根据(预期解析器的)类型返回解析器。所以类似

x.ConstructServicesUsing(t =>
{
  if (t.Name.Contains("SomeObjectTypeResolver"))
    return new SomeObjectTypeResolver();
  // add more if statements if you have more than 2 resolvers
  return new SomeOtherObjectTypeResolver();
});

0
投票

这是我在单元测试中使用Moq.AutoMocker的地方。它用作 IoC 容器,您可以在容器中注册模拟,然后要求 AutoMocker 构造所需类型的对象,并且将为构造函数参数注入模拟。

然后您只需配置 AutoMapper 以使用 AutoMocker 来构建其服务,以便您的解析器使用相同的行为。这是您已经使用 ConstructServicesUsing 的地方,但不是自己创建解析器,而是使用 AutoMocker 来构造请求类型的对象。

我已经更新了上面的示例代码,以展示如何使用 AutoMocker 完成它

var repositoryMock = new Mock<IRepository>();
repositoryMock.Setup(s => s.GetNumber()).Returns(new NumberModel { Number = 3 });

var autoMocker = new AutoMock();

var mapper = new MapperConfiguration(mc =>
{
    mc.AddProfile<NumberProfile>();
    mc.ConstructServicesUsing(type => autoMocker.CreateInstance(type));
}).CreateMapper();

autoMocker.Use(mapper);
autoMocker.Use(repositoryMock.Object);

var service = autoMocker.CreateInstance<Service>();
© www.soinside.com 2019 - 2024. All rights reserved.