如何在C#的CQRS模式中进行单元测试?附代码求指导
CQRS模式下命令与查询处理器的单元测试指南
针对你提供的CQRS命令和查询处理器,我们可以用Moq(模拟框架)和xUnit(测试框架)编写单元测试,核心思路是模拟数据库上下文(appDbContext),完全脱离真实数据库环境。
一、测试AddStudentHandler(添加学生命令处理器)
测试核心目标:
- 确认学生对象被正确添加到DbSet
- 确认
SaveChangesAsync方法被调用 - 验证返回的学生与传入的实例一致
测试代码示例:
using Xunit; using Moq; using Microsoft.EntityFrameworkCore; using System.Threading; using System.Threading.Tasks; public class AddStudentHandlerTests { [Fact] public async Task Handle_ShouldAddStudentAndReturnOriginalInstance() { // 1. 准备测试用的学生对象 var testStudent = new Student { Id = 1, Name = "张三", Age = 18 }; // 2. 模拟学生DbSet var mockStudentSet = new Mock<DbSet<Student>>(); // 3. 模拟数据库上下文,绑定DbSet var mockContext = new Mock<appDbContext>(); mockContext.Setup(c => c.students).Returns(mockStudentSet.Object); // 4. 初始化处理器并构造命令 var handler = new AddStudentHandler(mockContext.Object); var command = new AddStudentCommand(testStudent); // 5. 执行处理逻辑 var result = await handler.Handle(command, CancellationToken.None); // 6. 验证行为与结果 mockStudentSet.Verify(s => s.Add(testStudent), Times.Once); mockContext.Verify(c => c.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once); Assert.Equal(testStudent, result); } }
二、测试GetAllStudentHandler(获取所有学生查询处理器)
测试核心目标:
- 确认返回的学生集合与DbSet中的测试数据完全一致
测试代码示例:
using Xunit; using Moq; using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; public class GetAllStudentHandlerTests { [Fact] public async Task Handle_ShouldReturnAllStudentsFromMockedContext() { // 1. 准备测试数据集合 var testStudents = new List<Student> { new Student { Id = 1, Name = "张三", Age = 18 }, new Student { Id = 2, Name = "李四", Age = 19 } }; // 2. 将列表转为可查询的Mock DbSet var mockStudentSet = testStudents.AsQueryable().BuildMockDbSet(); // 3. 模拟数据库上下文 var mockContext = new Mock<appDbContext>(); mockContext.Setup(c => c.students).Returns(mockStudentSet.Object); // 4. 初始化处理器并构造查询 var handler = new GetAllStudentHandler(mockContext.Object); var query = new GetAllStudentDataQuery(); // 5. 执行查询逻辑 var result = await handler.Handle(query, CancellationToken.None); // 6. 验证结果 Assert.Equal(testStudents.Count, result.Count()); Assert.Equal(testStudents[0].Name, result.ElementAt(0).Name); } } // 扩展方法:快速将IQueryable转为Mock DbSet public static class MockDbSetExtensions { public static Mock<DbSet<T>> BuildMockDbSet<T>(this IQueryable<T> queryable) where T : class { var mockSet = new Mock<DbSet<T>>(); mockSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider); mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression); mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType); mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator()); return mockSet; } }
关键注意事项
- 单元测试只关注处理器自身的逻辑,无需验证EF Core的内置功能(比如
SaveChanges的底层执行) - 模拟DbContext时,要确保DbSet支持处理器用到的操作(添加、查询等)
- 测试取消场景时,可传入已取消的
CancellationToken,验证逻辑是否正确响应
内容的提问来源于stack exchange,提问作者Hansaka Dissa
相关产品推荐
相关产品推荐

