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

问题描述 投票:5回答:2

我正在尝试为我的代码添加单元测试,我使用来自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:未将对象引用设置为对象的实例

堆栈跟踪:

DevicePategoryPresenterTest.cs中的atPresenters.UnitTests.DeviceCategoryPresenterTest.InitializeView_Called()[0x00241]:56(托管管理到本机)System.Reflection.MonoMethod:InternalInvoke(System.Reflection.MonoMethod,object,object [],System.Exception& )在System.Reflection.MonoMethod.Invoke(System.Object obj,System.Reflection.BindingFlags invokeAttr,System.Reflection.Binder binder,System.Object [] parameters,System.Globalization.CultureInfo culture)[0x00038] in / private / TMP /源 - 单 - 4.8.0 / bockbuild - 单4.8.0分支/简档/单MAC-xamarin /建立根/单声道86 / MCS /类/ corlib /的System.Reflection / MonoMethod.cs: 305

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

c# unit-testing nunit task-parallel-library moq
2个回答
3
投票

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

.NOT <inc

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

.NOT> =上课时间

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);
© www.soinside.com 2019 - 2024. All rights reserved.