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); }
问题根源
你的测试逻辑完全没生效,原因有三个:
- 直接实例化了真实的
Job对象,调用_job.GetJob()时执行的是真实类方法,和Mock的IJob毫无关联; GetJob里调用的是自身的Get<T>方法,不是接口实现,所以Mock的接口方法不会被触发;- 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
相关产品推荐
相关产品推荐

