如何用xUnit、Moq结合内存数据库测试ASP.NET Core 6应用?
解决ASP.NET Core 6测试中内存数据库种子数据返回Null的问题
核心问题分析
你用Mock<IArtistaService>时,默认Mock对象不会自动关联Fixture里的内存上下文和真实仓储/服务逻辑,Mock方法默认返回null,这就是拿不到种子数据的根本原因。
解决方案步骤
1. 调整测试策略:用真实服务+内存上下文替代Mock服务
如果目标是验证控制器到数据库的完整业务逻辑,没必要Mock服务层,直接注入真实的服务、仓储和Fixture中的内存上下文即可。
正确的Fixture写法
public class DatabaseFixture : IDisposable { public OrientoonContext Context { get; private set; } public DatabaseFixture() { // 生成唯一内存库名,避免测试间数据污染 var options = new DbContextOptionsBuilder<OrientoonContext>() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; Context = new OrientoonContext(options); SeedTestData(Context); } private void SeedTestData(OrientoonContext context) { var testArtistas = new List<Artista> { new Artista { Id = 1, Nome = "测试艺术家1" }, new Artista { Id = 2, Nome = "测试艺术家2" } }; context.Artistas.AddRange(testArtistas); context.SaveChanges(); } public void Dispose() { Context.Dispose(); } } // 标记测试集合共享Fixture [CollectionDefinition("DatabaseCollection")] public class DatabaseCollection : ICollectionFixture<DatabaseFixture> { }
测试类实现
[Collection("DatabaseCollection")] public class ArtistaControllerTests { private readonly ArtistaController _controller; public ArtistaControllerTests(DatabaseFixture fixture) { // 实例化真实仓储和服务 var repository = new ArtistaRepository(fixture.Context); var service = new ArtistaService(repository); // 注入控制器 _controller = new ArtistaController(service); } [Fact] public async Task GetArtista_ReturnsValidSeedData() { var result = await _controller.GetArtista(1); var okResult = Assert.IsType<OkObjectResult>(result); var artista = Assert.IsType<Artista>(okResult.Value); Assert.NotNull(artista); Assert.Equal("测试艺术家1", artista.Nome); } }
2. 若必须Mock服务层,需配置Mock调用真实逻辑
如果场景要求必须MockIArtistaService,需要让Mock方法指向基于真实上下文的服务实现:
[Collection("DatabaseCollection")] public class ArtistaControllerTests { private readonly ArtistaController _controller; public ArtistaControllerTests(DatabaseFixture fixture) { var realRepository = new ArtistaRepository(fixture.Context); var realService = new ArtistaService(realRepository); var mockService = new Mock<IArtistaService>(); // 配置Mock方法调用真实服务逻辑 mockService.Setup(s => s.ObterPorId(It.IsAny<int>())) .ReturnsAsync((int id) => realService.ObterPorId(id)); _controller = new ArtistaController(mockService.Object); } [Fact] public async Task GetArtista_ReturnsSeedData() { var result = await _controller.GetArtista(2); var okResult = Assert.IsType<OkObjectResult>(result); var artista = Assert.IsType<Artista>(okResult.Value); Assert.NotNull(artista); Assert.Equal("测试艺术家2", artista.Nome); } }
3. Fixture常见问题排查
- 确保内存库名唯一:用
Guid.NewGuid().ToString()生成库名,避免不同测试共享数据导致污染。 - 验证种子数据写入:在Fixture的
SeedTestData方法中,调用SaveChanges()后检查context.Artistas.Count()是否符合预期,确认数据已写入。 - 检查上下文配置:确保内存数据库配置覆盖了生产环境的MariaDB配置,没有被其他DI配置覆盖。
关键注意点
- 测试控制器的端到端业务逻辑时,优先使用真实服务+内存数据库,Mock更适合隔离第三方依赖(如外部API),而非自身的服务和仓储层。
内容的提问来源于stack exchange,提问作者OlopesMaster
相关产品推荐
相关产品推荐

