.NET Core 8中Mock含私有依赖的UserRepository及问题解决
.NET Core 8 仓储类测试疑难解答
背景代码
UserRepository 实现
public class UserRepository : EfRepository<User>, IUserRepository { private readonly IJwtFactory _jwtFactory; private readonly IJwtTokenValidator _jwtTokenValidator; private readonly ITokenFactory _tokenFactory; private readonly UserManager<AppUser> _userManager; private readonly SignInManager<AppUser> _signInManager; private readonly IMapper _mapper; private readonly ILogger<UserRepository> _logger; public UserRepository(ILogger<UserRepository> logger, UserManager<AppUser> userManager, SignInManager<AppUser> signInManager, IMapper mapper, AppDbContext appDbContext, IJwtFactory jwtFactory, IJwtTokenValidator jwtTokenValidator, ITokenFactory tokenFactory) : base(appDbContext) { _userManager = userManager; _signInManager = signInManager; _jwtFactory = jwtFactory; _jwtTokenValidator = jwtTokenValidator; _tokenFactory = tokenFactory; _mapper = mapper; _logger = logger; } public async virtual Task<MyResponse> MethodUnderTest(string id) { _userManager.DoSomeThing(); // 需要 Mock 的对象 _signInManager.DoSomeThins(); // 需要 Mock 的对象 } }
测试基类代码
public class InfrastructureTestBase : IDisposable { public Mock<UserManager<AppUser>> UserManager { get; } = new Mock<UserManager<AppUser>>(Mock.Of<IUserStore<AppUser>>(), null, null, null, null, null, null, null, null); public Mock<SignInManager<AppUser>> SignInManager { get; } public Mock<ILogger<IUserRepository>> Logger { get; } = new Mock<ILogger<IUserRepository>>(); public Mock<IMapper> Mapper { get; } = new Mock<IMapper>(); public Mock<AppDbContext> AppDbContext { get; } = new Mock<AppDbContext>(new DbContextOptions<AppDbContext>()); public Mock<IJwtFactory> JwtFactory { get; } = new Mock<IJwtFactory>(); public Mock<IJwtTokenValidator> JwtTokenValidator { get; } = new Mock<IJwtTokenValidator>(); public Mock<UserRepository> UserRepository { get; private set; } public Mock<ITokenFactory> TokenFactory { get; } = new Mock<ITokenFactory>(); public InfrastructureTestBase() { SignInManager = new Mock<SignInManager<AppUser>>(UserManager.Object, Mock.Of<IHttpContextAccessor>(), Mock.Of<IUserClaimsPrincipalFactory<AppUser>>(), null, null, null, null); SignInManager.Setup(i => i.RefreshSignInAsync(It.IsAny<AppUser>())); Mapper.Setup(i => i.Map<AppUser, User>(It.IsAny<AppUser>(), It.IsAny<User>())).Returns(new User()); // 此处可能存在问题 UserRepository = new Mock<UserRepository>(Logger.Object, UserManager.Object, SignInManager.Object, Mapper.Object, AppDbContext.Object, JwtFactory.Object, JwtTokenValidator.Object, TokenFactory.Object); } public void Dispose() { //throw new NotImplementedException(); } }
额外问题代码(getUser 方法)
private async Task<User> getUser(AppUser appUser) => appUser == null ? null : _mapper.Map<AppUser, User>(appUser, await GetSingleBySpec(new UserSpecification(appUser.Id)));
问题汇总
- 应该 Mock
IUserRepository接口还是UserRepository对象?如何设置私有成员UserManager、SignInManager的方法调用?如何将这两个对象的 Mock 实例传入 Mock 的IUserRepository? - Mock
UserRepository对象时触发异常:System.ArgumentException : Can not instantiate proxy of class: Web.Api.Infrastructure.Data.Repositories.UserRepository. Could not find a constructor that would match given arguments: Castle.Proxies.ILogger`1Proxy Castle.Proxies.UserManager`1Proxy Castle.Proxies.SignInManager`1Proxy Castle.Proxies.IMapperProxy Castle.Proxies.AppDbContextProxy Castle.Proxies.IJwtFactoryProxy Castle.Proxies.IJwtTokenValidatorProxy Castle.Proxies.ITokenFactoryProxy (Parameter 'constructorArguments') ---- System.MissingMethodException : Constructor on type 'Castle.Proxies.UserRepositoryProxy' not found. - 将
UserManager作为SUT时,getUser方法触发 IMapper 映射空引用异常
解答
问题1:Mock 接口 vs 实现类的选择及依赖处理
- 优先 Mock
IUserRepository接口:遵循依赖倒置原则,测试时只关注接口约定的行为,无需关心内部实现。仅当需要测试UserRepository自身逻辑(将其作为SUT)时,才需要实例化它并Mock其依赖。 - 设置私有依赖的方法调用:若将
UserRepository作为SUT,无需Mock私有成员,直接通过构造函数注入依赖的Mock实例,再设置Mock对象的方法预期:// 示例:在测试类中构造依赖Mock var userManagerMock = new Mock<UserManager<AppUser>>(Mock.Of<IUserStore<AppUser>>(), null, null, null, null, null, null, null, null); userManagerMock.Setup(u => u.DoSomeThing()).Returns(/* 预期结果 */); var signInManagerMock = new Mock<SignInManager<AppUser>>(userManagerMock.Object, Mock.Of<IHttpContextAccessor>(), Mock.Of<IUserClaimsPrincipalFactory<AppUser>>(), null, null, null, null); signInManagerMock.Setup(s => s.DoSomeThins()).Returns(/* 预期结果 */); // 注入到真实的UserRepository实例 var userRepo = new UserRepository(loggerMock.Object, userManagerMock.Object, signInManagerMock.Object, mapperMock.Object, dbContextMock.Object, jwtFactoryMock.Object, jwtTokenValidatorMock.Object, tokenFactoryMock.Object); - 无需向Mock的IUserRepository传入依赖:Mock
IUserRepository时,直接模拟它暴露的方法即可,不需要接触其内部依赖。比如测试依赖IUserRepository的其他类时,只需设置IUserRepository的方法预期:var userRepoMock = new Mock<IUserRepository>(); userRepoMock.Setup(r => r.MethodUnderTest(It.IsAny<string>())).ReturnsAsync(new MyResponse());
问题2:Mock UserRepository 类的异常解决
异常核心原因:构造参数类型不匹配,且Moq无法为构造参数不匹配的类创建代理。具体错误点:
- 测试基类中
Logger的类型是Mock<ILogger<IUserRepository>>,但UserRepository构造函数需要的是ILogger<UserRepository>,类型完全不匹配。 - 解决步骤:
- 修正Logger的Mock类型,与构造参数一致:
public Mock<ILogger<UserRepository>> Logger { get; } = new Mock<ILogger<UserRepository>>(); - 若需Mock
UserRepository类,确保被测方法(如MethodUnderTest)是virtual修饰的(你的代码已满足),否则Moq无法覆盖方法。 - 优先选择直接实例化
UserRepository作为SUT,搭配依赖Mock的方式,比Mock整个UserRepository类更简单可靠。
- 修正Logger的Mock类型,与构造参数一致:
额外问题:getUser方法空引用异常解决
空引用源于 await GetSingleBySpec(...) 返回null,导致 _mapper.Map 的第二个参数为null引发异常,解决方式二选一:
- Mock继承的GetSingleBySpec方法:将
EfRepository<User>中的GetSingleBySpec设为virtual,然后在测试中覆盖该方法的返回值:// 在测试基类中添加 UserRepository.Setup(r => r.GetSingleBySpec(It.IsAny<UserSpecification>())).ReturnsAsync(new User()); - 补充Mapper的单参数映射Setup:当第二个参数为null时,AutoMapper会调用单参数重载的Map方法,补充该方法的预期:
Mapper.Setup(m => m.Map<User>(It.IsAny<AppUser>())).Returns(new User());
内容的提问来源于stack exchange,提问作者khteh
相关产品推荐
相关产品推荐

