Reputation: 1005
I am trying to add unit-tests for my code where I am using Task
from TPL to update values into a database. For unit-test, I am using NUnit
and Moq
. Here are some code snippets from my project.
*//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());
This is working. But when I add ContinueWith
with the service method call like this...
_ServiceFacade.SynchronizeDataset(DATASET_NAME).ContinueWith(t =>
{
if (t.IsFaulted)
{
//do something
}
});
this test code is not working.Test is failed and it shows this error...
System.NullReferenceException : Object reference not set to an instance of an object
Stacktrace:
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
and I am not sure how can I fix it. Please help. Thanks in advance.
Upvotes: 6
Views: 1650
Reputation: 9519
The fact here is that you are skipping your continuation by passing the valid task instead of It.IsAny<Task>
. The one example is to do something like this
.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))
You can even try to make your continuation with option TaskContinuationOptions.OnlyOnFaulted
because you are only interested in IsFaulted
scenario.
Be aware that you are not testing continuation part only skipping it. If you really want to test\verify continuation part be careful with it. It seems that your logic is service side logic so there TaskScheduler
will use default SynchronizationContext
and schedule continuation on the ThreadPool
thread. Of course this is executed within unit test runner context which is the same. Basically your tests could finish even before continuation task is executed.
Upvotes: 4
Reputation: 3853
In your setup you set up the function to return null
. You already stated this in a comment, It.IsAny<Task>()
returns null
.
Setup(sf => sf.SynchronizeDataset(It.IsAny<string>()))
.Returns(It.IsAny<Task>());
So if we break this down:
_ServiceFacade.SynchronizeDataset(DATASET_NAME).ContinueWith(t =>
{
if (t.IsFaulted)
{
//do something
}
});
... equals
// 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
Seems like you are writing an integration test which does not apply to your code (if your actual code does assume non-null as return). If this is the case, I suggest changing your setup to something like:
Setup(sf => sf.SynchronizeDataset(It.IsAny<string>()))
.Returns(Task.CompletedTask);
Upvotes: 1