You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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)));

问题汇总

  1. 应该 Mock IUserRepository 接口还是 UserRepository 对象?如何设置私有成员 UserManager、SignInManager 的方法调用?如何将这两个对象的 Mock 实例传入 Mock 的 IUserRepository?
  2. 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.
    
  3. 将 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>,类型完全不匹配。
  • 解决步骤:
    1. 修正Logger的Mock类型,与构造参数一致:
      public Mock<ILogger<UserRepository>> Logger { get; } = new Mock<ILogger<UserRepository>>();
      
    2. 若需Mock UserRepository 类,确保被测方法(如 MethodUnderTest)是 virtual 修饰的(你的代码已满足),否则Moq无法覆盖方法。
    3. 优先选择直接实例化 UserRepository 作为SUT,搭配依赖Mock的方式,比Mock整个 UserRepository 类更简单可靠。

额外问题:getUser方法空引用异常解决

空引用源于 await GetSingleBySpec(...) 返回null,导致 _mapper.Map 的第二个参数为null引发异常,解决方式二选一:

  1. Mock继承的GetSingleBySpec方法:将 EfRepository<User> 中的 GetSingleBySpec 设为 virtual,然后在测试中覆盖该方法的返回值:
    // 在测试基类中添加
    UserRepository.Setup(r => r.GetSingleBySpec(It.IsAny<UserSpecification>())).ReturnsAsync(new User());
    
  2. 补充Mapper的单参数映射Setup:当第二个参数为null时,AutoMapper会调用单参数重载的Map方法,补充该方法的预期:
    Mapper.Setup(m => m.Map<User>(It.IsAny<AppUser>())).Returns(new User());
    

内容的提问来源于stack exchange,提问作者khteh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 20:36:00