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

时间:2018-02-22 05:19:09

标签: c# unit-testing nunit task-parallel-library moq

我正在尝试为我的代码添加单元测试,我在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         at(wrapper managed-to-native)System.Reflection.MonoMethod:InternalInvoke   (System.Reflection.MonoMethod,对象,对象[],System.Exception的&安培)         在System.Reflection.MonoMethod.Invoke(System.Object obj,System.Reflection.BindingFlags invokeAttr,System.Reflection.Binder)   binder,System.Object []参数,System.Globalization.CultureInfo   文化)[0x00038] in   /private/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 个答案:

答案 0 :(得分:2)

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

.NET&lt; V4.6

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

.NET&gt; = v4.6

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

您甚至可以尝试使用选项TaskContinuationOptions.OnlyOnFaulted进行延续,因为您只对IsFaulted方案感兴趣。

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

答案 1 :(得分: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);