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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 03:45:35