首页 文章

当ContinueWith与System.Threading.Tasks.Task一起使用时,单元测试失败

提问于
浏览
5

我正在尝试为我的代码添加单元测试,我在TPL中使用 Task 将值更新到数据库中 . 对于单元测试,我使用 NUnitMoq . 以下是我项目中的一些代码片段 .

*//service*
public interface IServiceFacade{
   Task SynchronizeDataset (string datasetName);
}

*//The method call I want to test*
_ServiceFacade.SynchronizeDataset(DATASET_NAME);

*//In my test, I want to verify if this method is called*
mock_IServicesFacade.Setup(sf => sf.SynchronizeDataset(It.IsAny<string>())).Returns(It.IsAny<Task>());
presenter.InitializeView();
mock_IServicesFacade.Verify(sf => sf.SynchronizeDataset(NSUserUtilStrings.DATASET_ACHIEVEMENT), Times.Once());

这很有效 . 但是当我用这样的服务方法调用添加 ContinueWith 时......

_ServiceFacade.SynchronizeDataset(DATASET_NAME).ContinueWith(t =>
{
    if (t.IsFaulted)
    {
        //do something
    }
});

此测试代码无法正常工作 . 测试失败并显示此错误...

System.NullReferenceException:未将对象引用设置为对象的实例

堆栈跟踪:

atPresenters.UnitTests.DeviceCategoryPresenterTest.InitializeView_Called()[0x00241]在DeviceCategoryPresenterTest.cs:56(包装管理到本机)System.Reflection.MonoMethod:InternalInvoke(System.Reflection.MonoMethod,对象,对象[],系统 . 在System.Reflection.MonoMethod.Invoke例外&)(System.Object的物镜,System.Reflection.BindingFlags invokeAttr,System.Reflection.Binder Binders ,System.Object的[]参数,System.Globalization.CultureInfo培养物)[0x00038]在/私人/tmp/source-mono-4.8.0/bockbuild-mono-4.8.0-branch/profiles/mono-mac-xamarin/build-root/mono-x86/mcs/class/corlib/System.Reflection/MonoMethod.cs :305

我不知道如何解决它 . 请帮忙 . 提前致谢 .

2 回答

  • 2

    这里的事实是您通过传递有效任务而不是 It.IsAny<Task> 来跳过延续 . 一个例子是做这样的事情

    .NET < v4.6

    mock_IServicesFacade
        .Setup(sf => sf.SynchronizeDataset(It.IsAny<string>()))
        .Returns(Task.FromResult(true)))
    

    .NET >= v4.6

    mock_IServicesFacade
        .Setup(sf => sf.SynchronizeDataset(It.IsAny<string>()))
        .Returns(Task.CompletedTask))
    

    您甚至可以尝试使用选项 TaskContinuationOptions.OnlyOnFaulted 继续,因为您只对 IsFaulted 场景感兴趣 .

    请注意,您不是仅仅跳过它来测试延续部分 . 如果你真的想测试\验证延续部分要小心 . 看来您的逻辑是服务端逻辑,所以 TaskScheduler 将使用默认 SynchronizationContext 并在 ThreadPool 线程上安排延续 . 当然,这是在单元测试运行器上下文中执行的,它是相同的 . 基本上,即使在执行延续任务之前,您的测试也可以完成 .

  • 1

    在您的设置中,您设置函数以返回 null . 您已在评论中说明了这一点, It.IsAny<Task>() 返回 null .

    Setup(sf => sf.SynchronizeDataset(It.IsAny<string>()))
        .Returns(It.IsAny<Task>());
    

    所以,如果我们打破这个:

    _ServiceFacade.SynchronizeDataset(DATASET_NAME).ContinueWith(t =>
               {
                   if (t.IsFaulted)
                   {
                      //do something
                   }
               });
    

    ... 等于

    // This works, but returns null, so testing anything from this point is limited.
    var myNullTask = _ServiceFacade.SynchronizeDataset(DATASET_NAME);
    
    myNullTask.ContinueWith(t => ... ); // This yields NullReferenceException
    ((Task)null).ContinueWith(t => ... ); // Equivalent to line above
    

    好像你正在编写一个不适用于你的代码的集成测试(如果你的实际代码确实假设非null为return) . 如果是这种情况,我建议您将设置更改为:

    Setup(sf => sf.SynchronizeDataset(It.IsAny<string>()))
        .Returns(Task.CompletedTask);
    

相关问题