如何用Moq、xUnit和In-Memory Db测试CQRS仓储层方法
仓储层单元测试实现指南(Moq + xUnit + EF Core In-Memory)
先修正代码中的已知错误
在开始编写测试前,先修复你提供的代码里的几个拼写/逻辑错误,否则测试会失败:
IGenericRepository<TContext, TEntity>应该是GenericRepository<TContext, TEntity>(类实现接口,而非接口继承)- GamesRepository 中的
readomly改为readonly - GamesRepository 构造函数里的
_gamesDbContext - dbContext改为_gamesDbContext = dbContext - GetByGameNameAsync 方法里的
v.Entity == gameName改为v.gameName == gameName(实体类属性为gameName)
步骤1:安装必要NuGet包
在测试项目中安装以下包:
Install-Package Microsoft.EntityFrameworkCore.InMemory Install-Package xunit Install-Package xunit.runner.visualstudio Install-Package Moq Install-Package Microsoft.NET.Test.Sdk
步骤2:编写测试类
创建 GamesRepositoryTests 类,实现两个方法的单元测试:
using Xunit; using Microsoft.EntityFrameworkCore; using YourNamespace; // 替换为你的项目命名空间 public class GamesRepositoryTests { // 辅助方法:生成带测试数据的In-Memory DbContext private GamesDbContext CreateTestDbContext(List<GamesEntity> testData) { var options = new DbContextOptionsBuilder<GamesDbContext>() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) // 用唯一库名避免测试间数据干扰 .Options; var context = new GamesDbContext(options); context.GamesEntity.AddRange(testData); context.SaveChanges(); return context; } #region GetAllAsync 测试 [Fact] public async Task GetAllAsync_ReturnsAllEntities() { // 1. 准备测试数据 var testGames = new List<GamesEntity> { new GamesEntity { Id = 1, gameName = "原神", gameDescription = "开放世界RPG" }, new GamesEntity { Id = 2, gameName = "星穹铁道", gameDescription = "回合制RPG" } }; // 2. 创建In-Memory DbContext using var context = CreateTestDbContext(testGames); // 3. 实例化仓储 var repository = new GamesRepository(context); // 4. 调用待测试方法 var result = await repository.GetAllAsync(); // 5. 验证结果 Assert.NotNull(result); Assert.Equal(testGames.Count, result.Count); Assert.Contains(result, g => g.gameName == "原神"); Assert.Contains(result, g => g.gameName == "星穹铁道"); } #endregion #region GetByGameNameAsync 测试 [Fact] public async Task GetByGameNameAsync_ExistingName_ReturnsMatchingEntities() { // 1. 准备测试数据 var testGames = new List<GamesEntity> { new GamesEntity { Id = 1, gameName = "原神", gameDescription = "开放世界RPG" }, new GamesEntity { Id = 2, gameName = "星穹铁道", gameDescription = "回合制RPG" }, new GamesEntity { Id = 3, gameName = "原神", gameDescription = "国际服" } }; // 2. 创建In-Memory DbContext using var context = CreateTestDbContext(testGames); // 3. 实例化仓储 var repository = new GamesRepository(context); // 4. 调用待测试方法 var result = await repository.GetByGameNameAsync("原神"); // 5. 验证结果 Assert.NotNull(result); Assert.Equal(2, result.Count); Assert.All(result, g => Assert.Equal("原神", g.gameName)); } [Fact] public async Task GetByGameNameAsync_NonExistingName_ReturnsEmptyList() { // 1. 准备测试数据 var testGames = new List<GamesEntity> { new GamesEntity { Id = 1, gameName = "原神", gameDescription = "开放世界RPG" } }; // 2. 创建In-Memory DbContext using var context = CreateTestDbContext(testGames); // 3. 实例化仓储 var repository = new GamesRepository(context); // 4. 调用待测试方法 var result = await repository.GetByGameNameAsync("塞尔达传说"); // 5. 验证结果 Assert.NotNull(result); Assert.Empty(result); } #endregion }
关键说明
- In-Memory数据库优势:无需真实数据库连接,测试速度快,且每次测试用唯一库名避免数据污染。
- 测试流程:严格遵循「准备数据→创建依赖→调用方法→验证结果」的单元测试标准流程。
- 异步处理:所有测试方法标记
async,调用仓储方法时用await,确保异步逻辑正确执行。 - 边界覆盖:GetByGameNameAsync测试了「存在匹配数据」和「无匹配数据」两种场景,保证方法逻辑完整性。
内容的提问来源于stack exchange,提问作者LowKeyLo
相关产品推荐
相关产品推荐

