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.
ContinueWith(Action<Task,Object>, Object, TaskScheduler)Creates a continuation that receives caller-supplied state information and executes asynchronously when the target Task completes.
The ContinueWith function is a method available on the task that allows executing code after the task has finished execution. In simple words it allows continuation. Things to note here is that ContinueWith also returns one Task. That means you can attach ContinueWith one task returned by this method.
Unit testing is a software development process in which the smallest testable parts of an application, called units, are individually and independently scrutinized for proper operation. This testing methodology is done during the development process by the software developers and sometimes QA staff.
Unit tests do not check the integration between all the functional components of the application. They are, in fact, furthest from the end user experience. Today, you need to test the application end-to-end to drive quality.
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);
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With