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

MsTest单元测试中Moq模拟方法调用失败,求调试帮助

单元测试Mock问题求助:无法Mock Get方法,实际执行原方法

我是MsTest新手,正尝试为两个方法编写单元测试,已参考相关文章。

代码需求

  • 创建Azure定时函数
  • 按顺序执行方法1(GetJob)和方法2(Get)以获取输出

测试需求

  • 可为每个类/方法创建无外部依赖的单元测试(使用Fake/Mock)
  • 待测试代码可修改(尚未上线)
  • 可使用Moq之外的其他工具/NuGet包

当前运行单元测试时,无法Mock方法2(Get),而是实际执行了该方法,需要帮助调试代码。

代码实现

public class Job: IJob
{
    //Method 1
    public List<TableEntity> GetJob()
    {
        var a = Get<TableEntity>("static value"); //Mock this to Test Method GetJob
        return a.Result;
    }

   //Method 2
    public async Task<List<T>> Get<T>(string tableName) where T : ITableEntity, new()
    {
        var t = new List<T>();
        //add data to T
        return t;
    }
}

接口定义

public interface IJob
{
    List<TableEntity> GetJob();

    Task<List<T>> Get<T>(string tableName) where T : ITableEntity, new();
}

测试代码

private readonly Mock<IJob> _mockIJob = new Mock<IJob>();
readonly Job _job = new Job();

public void NotThrow_Error_When_JobFound()
{
    //Arrange
    var jobs = new TableEntity()
    {
        FolderName = "FolderName",
        Timestamp = DateTimeOffset.Now
    };

    var jobList = Task.FromResult(new List<TableEntity>() { jobs });
    
    _mockIJob.Setup(c => c.Get<TableEntity>(""))
            .Returns(jobList);

    //Act
    var actualResult = _job.GetJob();

    //Assert
    Assert.AreEqual(jobList, actualResult);
}

问题根源

你的测试逻辑完全没生效,原因有三个:

  1. 直接实例化了真实的Job对象,调用_job.GetJob()时执行的是真实类方法,和Mock的IJob毫无关联;
  2. GetJob里调用的是自身的Get<T>方法,不是接口实现,所以Mock的接口方法不会被触发;
  3. Setup里的参数是空字符串,但GetJob里调用的是"static value",参数不匹配,即使Mock生效也不会触发。

解决方案

方案一:最小改动——把Get改成虚方法

适合不想大幅重构的场景,只需修改Job类的Get<T>为虚方法,然后MockJob类本身:

修改后的Job类

public class Job: IJob
{
    public List<TableEntity> GetJob()
    {
        var a = Get<TableEntity>("static value");
        return a.Result;
    }

    // 改为虚方法,允许Moq拦截
    public virtual async Task<List<T>> Get<T>(string tableName) where T : ITableEntity, new()
    {
        var t = new List<T>();
        //add data to T
        return t;
    }
}

修改后的测试代码

private readonly Mock<Job> _mockJob = new Mock<Job>();

public void NotThrow_Error_When_JobFound()
{
    //Arrange
    var jobs = new TableEntity()
    {
        FolderName = "FolderName",
        Timestamp = DateTimeOffset.Now
    };

    var jobList = Task.FromResult(new List<TableEntity>() { jobs });
    
    // 参数要和GetJob里的调用完全匹配
    _mockJob.Setup(c => c.Get<TableEntity>("static value"))
            .Returns(jobList);

    //Act:调用Mock对象的GetJob,而非真实Job实例
    var actualResult = _mockJob.Object.GetJob();

    //Assert:对比实际结果和预期的列表(注意jobList是Task,要取Result)
    Assert.AreEqual(jobList.Result, actualResult);
}

方案二:规范重构——拆分数据访问职责(推荐)

更符合依赖倒置原则,把数据访问逻辑抽成独立接口,让Job类依赖这个接口,彻底解耦业务逻辑和数据访问:

定义数据访问接口

public interface ITableDataAccessor
{
    Task<List<T>> Get<T>(string tableName) where T : ITableEntity, new();
}

修改后的Job类

public class Job: IJob
{
    private readonly ITableDataAccessor _tableDataAccessor;

    // 构造注入数据访问接口
    public Job(ITableDataAccessor tableDataAccessor)
    {
        _tableDataAccessor = tableDataAccessor;
    }

    public List<TableEntity> GetJob()
    {
        // 调用依赖接口的方法,而非自身方法
        var a = _tableDataAccessor.Get<TableEntity>("static value");
        return a.Result;
    }

    // 如果IJob接口需要保留Get<T>,可以委托给数据访问层
    public async Task<List<T>> Get<T>(string tableName) where T : ITableEntity, new()
    {
        return await _tableDataAccessor.Get<T>(tableName);
    }
}

修改后的测试代码

private readonly Mock<ITableDataAccessor> _mockTableAccessor = new Mock<ITableDataAccessor>();

public void NotThrow_Error_When_JobFound()
{
    //Arrange
    var jobs = new TableEntity()
    {
        FolderName = "FolderName",
        Timestamp = DateTimeOffset.Now
    };

    var jobList = Task.FromResult(new List<TableEntity>() { jobs });
    
    _mockTableAccessor.Setup(c => c.Get<TableEntity>("static value"))
                      .Returns(jobList);

    // 注入Mock的数据访问接口实例化Job
    var job = new Job(_mockTableAccessor.Object);

    //Act
    var actualResult = job.GetJob();

    //Assert
    Assert.AreEqual(jobList.Result, actualResult);
}

额外注意点

  • 测试方法可以改成async Task类型,用await替代.Result,避免潜在的死锁问题:
    public async Task NotThrow_Error_When_JobFound()
    {
        // ... 其他代码不变
        var jobList = Task.FromResult(new List<TableEntity>() { jobs });
        _mockTableAccessor.Setup(...).Returns(jobList);
        
        var job = new Job(_mockTableAccessor.Object);
        var actualResult = await job.GetJobAsync(); // 建议把GetJob改成异步方法,返回Task<List<TableEntity>>
        
        Assert.AreEqual(jobList.Result, actualResult);
    }
    
  • 确保Setup的参数和实际调用的参数完全匹配,Moq只会拦截参数一致的调用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 22:56:29