C#如何对仓储模式进行单元测试?认证控制器与数据库仓储测试求解
认证控制器与仓储测试最佳实践
一、控制器单元测试(解决依赖注入问题)
你遇到的依赖注入问题大概率是没必要引入DI容器导致的,控制器单元测试直接手动传入模拟依赖即可,不需要依赖ASP.NET Core的DI体系,配合Moq的实现步骤如下:
- 安装必要NuGet包:
Moq、xUnit(或NUnit)、Microsoft.AspNetCore.Mvc.Testing(可选,仅需要模拟HttpContext时安装) - 为每个测试场景模拟所有控制器依赖,按测试场景设置依赖返回值,再手动实例化控制器
- 调用对应接口方法,断言返回结果符合预期,同时验证依赖方法是否被正确调用
注册接口测试示例
using Moq; using Xunit; using Microsoft.AspNetCore.Mvc; using AuthenticationServerAPI.Controllers; using AuthenticationServerAPI.Models.Requests; using AuthenticationServerAPI.Services.UserRepositories; using AuthenticationServerAPI.Services.PasswordHashers; // 其他必要using public class AuthenticationControllerTests { // 预先声明所有模拟依赖 private readonly Mock<IUserRepository> _mockUserRepo = new(); private readonly Mock<IPasswordHasher> _mockPasswordHasher = new(); private readonly Mock<Authenticator> _mockAuthenticator = new(); private readonly Mock<RefreshTokenValidator> _mockRefreshTokenValidator = new(); private readonly Mock<IRefreshTokenRepository> _mockRefreshTokenRepo = new(); private readonly AuthenticationController _controller; public AuthenticationControllerTests() { // 手动注入所有模拟依赖,实例化控制器 _controller = new AuthenticationController( _mockUserRepo.Object, _mockPasswordHasher.Object, _mockAuthenticator.Object, _mockRefreshTokenValidator.Object, _mockRefreshTokenRepo.Object); } [Fact] public async Task Register_ValidRequest_ReturnsOkAndCreatesUser() { // Arrange var registerRequest = new RegisterRequest { Email = "test@test.com", Username = "testuser", Password = "Test@123", ConfirmPassword = "Test@123" }; // 模拟邮箱、用户名均未被注册 _mockUserRepo.Setup(r => r.GetByEmail(registerRequest.Email)) .ReturnsAsync((User)null); _mockUserRepo.Setup(r => r.GetByUsername(registerRequest.Username)) .ReturnsAsync((User)null); _mockPasswordHasher.Setup(h => h.HashPassword(registerRequest.Password)) .Returns("hashed_password"); // Act var result = await _controller.Register(registerRequest); // Assert Assert.IsType<OkResult>(result); // 验证用户创建方法被调用一次,参数符合预期 _mockUserRepo.Verify(r => r.Create(It.Is<User>(u => u.Email == registerRequest.Email && u.Username == registerRequest.Username && u.PasswordHash == "hashed_password" )), Times.Once); } [Fact] public async Task Register_EmailExists_ReturnsConflict() { // Arrange var registerRequest = new RegisterRequest { Email = "exist@test.com", Username = "testuser", Password = "Test@123", ConfirmPassword = "Test@123" }; _mockUserRepo.Setup(r => r.GetByEmail(registerRequest.Email)) .ReturnsAsync(new User()); // Act var result = await _controller.Register(registerRequest); // Assert Assert.IsType<ConflictObjectResult>(result); _mockUserRepo.Verify(r => r.Create(It.IsAny<User>()), Times.Never); } }
其余接口(登录、刷新Token、登出)的测试逻辑同理,按分支场景分别写测试用例即可。登出接口需要模拟HttpContext的User Claims,示例如下:
// 在Arrange阶段添加模拟用户Claims var claims = new ClaimsPrincipal(new ClaimsIdentity(new Claim[] { new Claim("id", Guid.NewGuid().ToString()) }, "TestAuth")); _controller.ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = claims } };
二、DatabaseUserRepository集成测试
仓储层直接和数据库交互,使用内存数据库做集成测试的收益远高于单元测试(Mock DbContext无法验证真实数据库交互逻辑),实现步骤如下:
- 安装NuGet包:
Microsoft.EntityFrameworkCore.InMemory - 每个测试用例初始化独立的内存数据库,避免测试间数据干扰
- 准备测试数据后调用仓储方法,断言返回结果符合预期
仓储测试示例
using Xunit; using Microsoft.EntityFrameworkCore; using AuthenticationServerAPI.Models; using AuthenticationServerAPI.Services.UserRepositories; public class DatabaseUserRepositoryTests { private async Task<AuthenticationDbContext> GetTestDbContextAsync() { // 每个测试用例使用独立的数据库名,避免数据冲突 var options = new DbContextOptionsBuilder<AuthenticationDbContext>() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; var context = new AuthenticationDbContext(options); // 可以预先插入测试数据 context.Users.Add(new User { Id = Guid.NewGuid(), Username = "existuser", Email = "exist@test.com", PasswordHash = "hashed_pwd" }); await context.SaveChangesAsync(); return context; } [Fact] public async Task GetByUsername_UserExists_ReturnsCorrectUser() { // Arrange var context = await GetTestDbContextAsync(); var repo = new DatabaseUserRepository(context); // Act var result = await repo.GetByUsername("existuser"); // Assert Assert.NotNull(result); Assert.Equal("exist@test.com", result.Email); } [Fact] public async Task Create_ValidUser_SavesToDatabase() { // Arrange var context = await GetTestDbContextAsync(); var repo = new DatabaseUserRepository(context); var newUser = new User { Id = Guid.NewGuid(), Username = "newuser", Email = "new@test.com", PasswordHash = "new_hashed_pwd" }; // Act await repo.Create(newUser); var savedUser = await context.Users.FirstOrDefaultAsync(u => u.Username == "newuser"); // Assert Assert.NotNull(savedUser); Assert.Equal("new@test.com", savedUser.Email); } }
注意:你的Logout方法中
_refreshTokenRepository.DeleateAll(userId)存在拼写错误,应为DeleteAll,测试时会触发异常,建议先修复。
内容的提问来源于stack exchange,提问作者Tom van den Bogaart
相关产品推荐
相关产品推荐

