.NET Core中无需配置IService Mock IOptions读取secret.json的Xunit测试方案
实现方案
你不需要在测试项目中配置依赖注入服务容器,直接手动构造控制器所有依赖的模拟/测试实例,再实例化SeedController执行测试逻辑即可,具体实现步骤如下:
前置依赖
先安装测试所需NuGet包:
xunitMoqMicrosoft.EntityFrameworkCore.InMemoryMicrosoft.AspNetCore.Identity.EntityFrameworkCoreMicrosoft.Extensions.Options
测试代码示例
using Xunit; using Moq; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using Microsoft.AspNetCore.Hosting; // 替换为你自己的项目命名空间 using YourProjectNamespace.Data; using YourProjectNamespace.Controllers; using YourProjectNamespace.Models; namespace YourProjectNamespace.Tests; public class SeedControllerTests { // 构造测试用的默认用户配置 private readonly DefaultUserOptions _testOptions = new() { Admin_Email = "admin@test.com", Admin_Password = "Test@123456", User_Email = "user@test.com", User_Password = "Test@123456" }; [Fact] public async Task CreateDefaultUsers_FirstRun_AddsTwoUsers() { // 1. 构造内存数据库的DbContext,无需真实数据库 var dbOptions = new DbContextOptionsBuilder<ApplicationDbContext>() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; using var context = new ApplicationDbContext(dbOptions); // 2. 构造Mock的RoleManager var roleStoreMock = new Mock<IRoleStore<IdentityRole>>(); var roleManager = new RoleManager<IdentityRole>(roleStoreMock.Object, null, null, null, null); // 模拟首次查询角色不存在 roleStoreMock.Setup(x => x.FindByNameAsync("RegisteredUser", CancellationToken.None)) .ReturnsAsync((IdentityRole)null); roleStoreMock.Setup(x => x.FindByNameAsync("Administrator", CancellationToken.None)) .ReturnsAsync((IdentityRole)null); roleStoreMock.Setup(x => x.CreateAsync(It.IsAny<IdentityRole>(), CancellationToken.None)) .ReturnsAsync(IdentityResult.Success); // 3. 构造Mock的UserManager var userStoreMock = new Mock<IUserStore<ApplicationUser>>(); // 若测试涉及密码哈希逻辑,可额外Mock IPasswordHasher传入 var passwordHasherMock = new Mock<IPasswordHasher<ApplicationUser>>(); passwordHasherMock.Setup(x => x.HashPassword(It.IsAny<ApplicationUser>(), It.IsAny<string>())).Returns("test_hash_value"); var userManager = new UserManager<ApplicationUser>(userStoreMock.Object, null, passwordHasherMock.Object, null, null, null, null, null, null); // 模拟首次查询用户不存在 userStoreMock.Setup(x => x.FindByNameAsync(_testOptions.Admin_Email, CancellationToken.None)) .ReturnsAsync((ApplicationUser)null); userStoreMock.Setup(x => x.FindByNameAsync(_testOptions.User_Email, CancellationToken.None)) .ReturnsAsync((ApplicationUser)null); userStoreMock.Setup(x => x.CreateAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>(), CancellationToken.None)) .ReturnsAsync(IdentityResult.Success); userStoreMock.Setup(x => x.AddToRoleAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>(), CancellationToken.None)) .ReturnsAsync(IdentityResult.Success); userStoreMock.Setup(x => x.UpdateAsync(It.IsAny<ApplicationUser>(), CancellationToken.None)) .ReturnsAsync(IdentityResult.Success); // 4. 构造Mock的IWebHostEnvironment(现有代码未用到该依赖,直接构造空实例即可) var envMock = new Mock<IWebHostEnvironment>(); // 5. 构造IOptions配置实例 var options = Options.Create(_testOptions); // 6. 手动实例化控制器,完全不需要依赖DI容器 var controller = new SeedController(context, roleManager, userManager, envMock.Object, options); // 7. 执行测试方法 var result = await controller.CreateDefaultUsers(); // 8. 断言结果符合预期 var jsonResult = Assert.IsType<JsonResult>(result); dynamic resultData = jsonResult.Value; Assert.Equal(2, resultData.Count); // 验证核心方法调用次数符合预期 roleStoreMock.Verify(x => x.CreateAsync(It.Is<IdentityRole>(r => r.Name == "RegisteredUser"), CancellationToken.None), Times.Once); roleStoreMock.Verify(x => x.CreateAsync(It.Is<IdentityRole>(r => r.Name == "Administrator"), CancellationToken.None), Times.Once); userStoreMock.Verify(x => x.CreateAsync(It.Is<ApplicationUser>(u => u.Email == _testOptions.Admin_Email), _testOptions.Admin_Password, CancellationToken.None), Times.Once); userStoreMock.Verify(x => x.CreateAsync(It.Is<ApplicationUser>(u => u.Email == _testOptions.User_Email), _testOptions.User_Password, CancellationToken.None), Times.Once); } [Fact] public async Task CreateDefaultUsers_UsersExist_ReturnsZeroCount() { // 1. 构造内存数据库的DbContext var dbOptions = new DbContextOptionsBuilder<ApplicationDbContext>() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; using var context = new ApplicationDbContext(dbOptions); // 2. 构造Mock的RoleManager,模拟角色已存在 var roleStoreMock = new Mock<IRoleStore<IdentityRole>>(); var roleManager = new RoleManager<IdentityRole>(roleStoreMock.Object, null, null, null, null); roleStoreMock.Setup(x => x.FindByNameAsync(It.IsAny<string>(), CancellationToken.None)) .ReturnsAsync(new IdentityRole()); // 3. 构造Mock的UserManager,模拟用户已存在 var userStoreMock = new Mock<IUserStore<ApplicationUser>>(); var passwordHasherMock = new Mock<IPasswordHasher<ApplicationUser>>(); var userManager = new UserManager<ApplicationUser>(userStoreMock.Object, null, passwordHasherMock.Object, null, null, null, null, null, null); userStoreMock.Setup(x => x.FindByNameAsync(It.IsAny<string>(), CancellationToken.None)) .ReturnsAsync(new ApplicationUser()); // 4. 构造其他依赖 var envMock = new Mock<IWebHostEnvironment>(); var options = Options.Create(_testOptions); // 5. 实例化控制器 var controller = new SeedController(context, roleManager, userManager, envMock.Object, options); // 6. 执行测试 var result = await controller.CreateDefaultUsers(); var jsonResult = Assert.IsType<JsonResult>(result); dynamic resultData = jsonResult.Value; // 7. 断言没有新增用户 Assert.Equal(0, resultData.Count); userStoreMock.Verify(x => x.CreateAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>(), CancellationToken.None), Times.Never); } }
说明
- 所有依赖项均为手动构造或通过Moq模拟,完全不需要配置
IServiceCollection服务容器,符合你的测试要求 - 测试覆盖了首次执行播种和重复执行播种两个核心场景,你可以根据自身需求扩展其他测试用例
- 如果不需要完全隔离测试,也可以替换掉Mock的RoleManager和UserManager,用内存数据库配合真实的Identity存储实现更贴近生产环境的测试逻辑
内容的提问来源于stack exchange,提问作者Ratul
相关产品推荐
相关产品推荐

