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

使用xUnit与Moq做单元测试时,TeacherController返回空Model集合排查

TeacherController Index方法返回Model集合为空的原因与解决办法

问题场景

调试TeacherController的Index方法时,单元测试中发现返回的Model集合为空,使用Moq和xUnit进行测试。

相关代码

TeacherController

public class TeacherController : Controller
{
    private IClassScheduleUnitOfWork _classScheduleUnitOfWork { get; set; }

    public TeacherController(IClassScheduleUnitOfWork classScheduleUnitOfWork)
    {
        _classScheduleUnitOfWork = classScheduleUnitOfWork;
    }

    public ViewResult Index()
    {
        var options = new QueryOptions<Teacher> {
            OrderBy = t => t.LastName
        };
        IEnumerable<Teacher> list = _classScheduleUnitOfWork.teachers.List(options);
        return View("Index", list);
    }
}

单元测试代码

public class TeacherControllerTests
{
    private readonly Mock<IClassScheduleUnitOfWork> _mock;

    public TeacherControllerTests()
    {
        _mock = new Mock<IClassScheduleUnitOfWork>();
    }

    [Fact]
    public void IndexActionMethod_ReturnsAViewResult()
    {
        var options = new QueryOptions<Teacher>
        {
            OrderBy = t => t.LastName
        };

        IEnumerable<Teacher> teachersList = GetAllTeachers();
 
        _mock.Setup(x =>x.teachers.List(options))
            .Returns(teachersList);

        var controller = new TeacherController(_mock.Object);

        //act
        var result = controller.Index();
        var actualData = (IEnumerable<Teacher>?)result?.ViewData.Model;
        Assert.Equal(5, actualData?.Count());

   }

   private IEnumerable<Teacher> GetAllTeachers()
   {
        IEnumerable<Teacher> teachers = new List<Teacher>()
        {
           new Teacher { TeacherId = 1, FirstName = "Anne", LastName = "Sullivan" },
           new Teacher { TeacherId = 2, FirstName = "Maria", LastName = "Montessori" },
           new Teacher { TeacherId = 3, FirstName = "Martin Luther", LastName = "King" },
           new Teacher { TeacherId = 4, FirstName = "", LastName = "Aristotle" },
           new Teacher { TeacherId = 5, FirstName = "Jaime", LastName = "Escalante" }
       };

       teachers = teachers.OrderBy(t => t.LastName).ToList();

       return teachers;
   }
}

IClassScheduleUnitOfWork定义

public interface IClassScheduleUnitOfWork
{
    public IRepository<Teacher> teachers { get; set; }
    public IRepository<Class> classes { get; set; }
    public IRepository<Day> days { get; set; }

    public void Save();
}

IRepository代码

public interface IRepository<T> where T : class
{
    IEnumerable<T> List(QueryOptions<T> options);
    T? Get(int id);
    T? Get(QueryOptions<T> queryOptions);
    void Insert(T entity);
    void Update(T entity);
    void Delete(T entity);
    void Save();
}

问题原因

核心问题在于Moq的参数匹配逻辑:
测试代码中,Setup时指定的是自己创建的options实例,但控制器的Index方法会新建一个独立的QueryOptions实例。Moq默认使用引用相等来匹配参数——这两个实例是不同的对象,因此Moq的Setup不会被触发,最终返回List方法的默认值(空集合),导致View的Model为空。

解决办法

方法1:匹配任意QueryOptions实例(最简单)

修改测试中的Setup代码,使用It.IsAny<QueryOptions<Teacher>>()匹配任意类型的参数,确保控制器传入的新实例能触发Setup:

_mock.Setup(x => x.teachers.List(It.IsAny<QueryOptions<Teacher>>()))
     .Returns(teachersList);

方法2:匹配符合特定条件的QueryOptions(更精准)

如果需要验证控制器传入的QueryOptions是否符合预期(比如OrderBy确实是按LastName排序),可以使用It.Is<QueryOptions<Teacher>>()进行条件匹配:

_mock.Setup(x => x.teachers.List(It.Is<QueryOptions<Teacher>>(opt => 
    // 验证OrderBy表达式是否正确
    opt.OrderBy != null && 
    opt.OrderBy.Compile()(new Teacher { LastName = "Z" }).Equals("Z") &&
    opt.OrderBy.Compile()(new Teacher { LastName = "A" }).Equals("A")
)))
.Returns(teachersList);

方法3:让QueryOptions实现值相等匹配

修改QueryOptions<T>类,实现IEquatable<QueryOptions<T>>接口,重写相等比较逻辑,让Moq可以按值而非引用匹配参数。这种方式适合需要严格匹配参数所有属性的场景,但需要修改QueryOptions的源码。

修改后的测试代码示例(方法1)

[Fact]
public void IndexActionMethod_ReturnsAViewResult()
{
    IEnumerable<Teacher> teachersList = GetAllTeachers();
 
    // 使用It.IsAny匹配任意QueryOptions实例
    _mock.Setup(x => x.teachers.List(It.IsAny<QueryOptions<Teacher>>()))
         .Returns(teachersList);

    var controller = new TeacherController(_mock.Object);

    //act
    var result = controller.Index();
    var actualData = (IEnumerable<Teacher>?)result?.ViewData.Model;
    Assert.Equal(5, actualData?.Count());
}

内容的提问来源于stack exchange,提问作者Affan Sheikh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 05:07:50