如何在断言Mock任务前等待ContinueWith执行完成?
问题:异步延续任务执行晚于断言导致测试失败
被测代码
public class ClassUnderTest { readonly IMyInterface dependency; public ClassUnderTest(IMyInterface dependency) { this.dependency = dependency; } public bool didFault = false; public void MethodUnderTest() { var task = dependency.DoSomethingAsync(); if (task != null) { task.ContinueWith(t => { if (t.IsFaulted) { didFault = true; } }); } } }
测试代码
[TestCase(TestName = "My test")] public void MyTestCase() { Mock<IMyInterface> mock = MockRepository.Create<IMyInterface>(); ClassUnderTest cut = new ClassUnderTest(mock.Object); mock.Setup(m => m.DoSomethingAsync()).ThrowsAsync(new Exception()); cut.MethodUnderTest(); Assert.True(cut.didFault); }
问题核心是MethodUnderTest中通过ContinueWith注册的延续任务,会在DoSomethingAsync返回的任务完成后异步执行,但测试代码调用MethodUnderTest后立刻执行断言,此时延续任务还未运行,导致断言失败。
解决方案
方案1:改造被测方法为异步方法(推荐)
将MethodUnderTest改为异步方法,用await替代ContinueWith,代码更易读的同时,也能直接在测试中等待任务完成:
修改后的被测代码
public class ClassUnderTest { readonly IMyInterface dependency; public ClassUnderTest(IMyInterface dependency) { this.dependency = dependency; } public bool didFault = false; public async Task MethodUnderTestAsync() { try { var task = dependency.DoSomethingAsync(); if (task != null) { await task; } } catch { didFault = true; } } }
修改后的测试代码
[TestCase(TestName = "My test")] public async Task MyTestCase() { Mock<IMyInterface> mock = MockRepository.Create<IMyInterface>(); ClassUnderTest cut = new ClassUnderTest(mock.Object); mock.Setup(m => m.DoSomethingAsync()).ThrowsAsync(new Exception()); await cut.MethodUnderTestAsync(); Assert.True(cut.didFault); }
方案2:不修改被测代码,在测试中等待延续任务完成
如果无法修改原类代码,可以通过控制任务执行流程,等待延续任务完成后再断言:
[TestCase(TestName = "My test")] public void MyTestCase() { // 创建一个可控的故障任务 var taskSource = new TaskCompletionSource<object>(); taskSource.SetException(new Exception()); var faultedTask = taskSource.Task; Mock<IMyInterface> mock = MockRepository.Create<IMyInterface>(); mock.Setup(m => m.DoSomethingAsync()).Returns(faultedTask); ClassUnderTest cut = new ClassUnderTest(mock.Object); cut.MethodUnderTest(); // 先等待原始故障任务完成 faultedTask.Wait(); // 循环检查状态+短延迟,带超时避免无限等待 var timeout = TimeSpan.FromSeconds(1); var start = DateTime.Now; while (!cut.didFault && DateTime.Now - start < timeout) { Task.Delay(10).Wait(); } Assert.True(cut.didFault); }
内容的提问来源于stack exchange,提问作者T'n'E
相关产品推荐
相关产品推荐

