如何在单元测试中手动模拟基于AutoMapper实现的IObjectMapper
如何在单元测试中手动模拟基于AutoMapper的ABP IObjectMapper(不加载ABP框架)
问题场景
在ABP项目中实现了OrderRepository,它依赖ABP提供的IObjectMapper(默认基于AutoMapper实现,已通过DbEntityToDomainModelProfile配置了OrderDbEntity到Order的映射规则)。当前使用NSubstitute编写单元测试,不想加载ABP框架,同时希望复用现有AutoMapper映射配置,避免为每个测试手动模拟Map方法的返回值。
解决方案
核心思路是直接基于现有AutoMapper配置创建真实的IMapper实例,让IObjectMapper的替代实例(Substitute)委托该实例处理映射逻辑,无需依赖ABP框架。
具体步骤
初始化AutoMapper配置
加载项目中已定义的DbEntityToDomainModelProfile,创建MapperConfiguration并验证配置有效性(可选但推荐)。生成真实IMapper实例
通过配置生成可执行映射的IMapper实例。配置IObjectMapper替代实例
让NSubstitute生成的IObjectMapper替代实例,在调用Map方法时,委托给真实的IMapper执行映射操作。
完整测试代码示例
using Xunit; using NSubstitute; using AutoMapper; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; public class OrderRepositoryTests { public class GetAllAsync { [Fact] public async Task WhenThereIsNoOrder_ShouldReturnEmptyList() { // Arrange // 1. 初始化AutoMapper配置,复用现有映射Profile var mapperConfig = new MapperConfiguration(cfg => { cfg.AddProfile<DbEntityToDomainModelProfile>(); }); // 验证映射配置,确保无错误 mapperConfig.AssertConfigurationIsValid(); // 2. 创建真实的IMapper实例 var realMapper = mapperConfig.CreateMapper(); // 模拟DbContext返回空订单列表 var mockDbContext = Substitute.For<OrderingDbContext>(); mockDbContext.Orders.ToListAsync(Arg.Any<CancellationToken>()) .Returns(Task.FromResult(new List<OrderDbEntity>())); // 3. 配置IObjectMapper替代实例,委托真实IMapper处理映射 var mockObjectMapper = Substitute.For<IObjectMapper>(); mockObjectMapper .Map<IEnumerable<OrderDbEntity>, IEnumerable<Order>>(Arg.Any<IEnumerable<OrderDbEntity>>()) .Returns(callInfo => { var sourceEntities = callInfo.Arg<IEnumerable<OrderDbEntity>>(); return realMapper.Map<IEnumerable<Order>>(sourceEntities); }); var sut = new OrderRepository(mockDbContext, mockObjectMapper); // Act var orders = await sut.GetAllAsync(); // Assert Assert.Empty(orders); await mockDbContext.Orders.Received(1).ToListAsync(Arg.Any<CancellationToken>()); mockObjectMapper.Received(1).Map<IEnumerable<OrderDbEntity>, IEnumerable<Order>>(Arg.Any<IEnumerable<OrderDbEntity>>()); } } }
复用AutoMapper配置的优化
如果多个测试方法都需要使用AutoMapper配置,可以将初始化逻辑提取到IClassFixture中,避免重复初始化:
public class OrderRepositoryTests : IClassFixture<AutoMapperFixture> { private readonly IMapper _realMapper; public OrderRepositoryTests(AutoMapperFixture fixture) { _realMapper = fixture.Mapper; } // 测试方法... } public class AutoMapperFixture { public IMapper Mapper { get; } public AutoMapperFixture() { var mapperConfig = new MapperConfiguration(cfg => { cfg.AddProfile<DbEntityToDomainModelProfile>(); }); mapperConfig.AssertConfigurationIsValid(); Mapper = mapperConfig.CreateMapper(); } }
这种方式既保留了单元测试的独立性,又复用了现有映射规则,避免了手动模拟映射返回值的冗余工作。
内容的提问来源于stack exchange,提问作者David Liang
相关产品推荐
相关产品推荐

