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

C#如何对仓储模式进行单元测试?认证控制器与数据库仓储测试求解

认证控制器与仓储测试最佳实践

一、控制器单元测试(解决依赖注入问题)

你遇到的依赖注入问题大概率是没必要引入DI容器导致的,控制器单元测试直接手动传入模拟依赖即可,不需要依赖ASP.NET Core的DI体系,配合Moq的实现步骤如下:

  1. 安装必要NuGet包:Moq、xUnit(或NUnit)、Microsoft.AspNetCore.Mvc.Testing(可选,仅需要模拟HttpContext时安装)
  2. 为每个测试场景模拟所有控制器依赖,按测试场景设置依赖返回值,再手动实例化控制器
  3. 调用对应接口方法,断言返回结果符合预期,同时验证依赖方法是否被正确调用

注册接口测试示例

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无法验证真实数据库交互逻辑),实现步骤如下:

  1. 安装NuGet包:Microsoft.EntityFrameworkCore.InMemory
  2. 每个测试用例初始化独立的内存数据库,避免测试间数据干扰
  3. 准备测试数据后调用仓储方法,断言返回结果符合预期

仓储测试示例

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 14:54:05