当 ContinueWith 与 System.Threading.Tasks.Task 一起使用时,单元测试失败
Unit test is failing when ContinueWith is used with System.Threading.Tasks.Task
我正在尝试为我的代码添加单元测试,我正在使用来自 TPL 的 Task
将值更新到数据库中。对于单元测试,我使用 NUnit
和 Moq
。这是我项目中的一些代码片段。
*//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
}
});
此测试代码不是 working.Test 失败并显示此错误...
System.NullReferenceException : Object reference not set to an
instance of an object
堆栈跟踪:
atPresenters.UnitTests.DeviceCategoryPresenterTest.InitializeView_Called
() [0x00241] in DeviceCategoryPresenterTest.cs:56
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke
(System.Reflection.MonoMethod,object,object[],System.Exception&)
at 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/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
而且我不确定该如何解决。请帮忙。提前致谢。
在您的设置中,您将函数设置为 return null
。您已经在评论中说明了这一点,It.IsAny<Task>()
returns 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
似乎您正在编写不适用于您的代码的集成测试(如果您的实际代码确实假定 non-null 为 return)。如果是这种情况,我建议将您的设置更改为:
Setup(sf => sf.SynchronizeDataset(It.IsAny<string>()))
.Returns(Task.CompletedTask);
这里的事实是,您通过传递有效任务而不是 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
场景感兴趣。
请注意,您不是在测试延续部分,只是跳过它。如果你真的想要 test\verify 延续部分,请小心。看来您的逻辑是服务端逻辑,因此 TaskScheduler
将使用默认 SynchronizationContext
并在 ThreadPool
线程上安排继续。当然,这是在相同的单元测试运行器上下文中执行的。基本上你的测试甚至可以在继续任务执行之前完成。
我正在尝试为我的代码添加单元测试,我正在使用来自 TPL 的 Task
将值更新到数据库中。对于单元测试,我使用 NUnit
和 Moq
。这是我项目中的一些代码片段。
*//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
}
});
此测试代码不是 working.Test 失败并显示此错误...
System.NullReferenceException : Object reference not set to an instance of an object
堆栈跟踪:
atPresenters.UnitTests.DeviceCategoryPresenterTest.InitializeView_Called () [0x00241] in DeviceCategoryPresenterTest.cs:56 at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&) at 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/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
而且我不确定该如何解决。请帮忙。提前致谢。
在您的设置中,您将函数设置为 return null
。您已经在评论中说明了这一点,It.IsAny<Task>()
returns 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
似乎您正在编写不适用于您的代码的集成测试(如果您的实际代码确实假定 non-null 为 return)。如果是这种情况,我建议将您的设置更改为:
Setup(sf => sf.SynchronizeDataset(It.IsAny<string>()))
.Returns(Task.CompletedTask);
这里的事实是,您通过传递有效任务而不是 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
场景感兴趣。
请注意,您不是在测试延续部分,只是跳过它。如果你真的想要 test\verify 延续部分,请小心。看来您的逻辑是服务端逻辑,因此 TaskScheduler
将使用默认 SynchronizationContext
并在 ThreadPool
线程上安排继续。当然,这是在相同的单元测试运行器上下文中执行的。基本上你的测试甚至可以在继续任务执行之前完成。