Unit test does not work when ContinueWith is used with System.Threading.Tasks.Task

I am trying to add unit tests for my code, where I use Task from TPL to update values ​​in the database. For unit-test, I use 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()); 

It works. But when I add ContinueWith with a service method call like this ...

 _ServiceFacade.SynchronizeDataset(DATASET_NAME).ContinueWith(t => { if (t.IsFaulted) { //do something } }); 

This test code does not work. The test failed and it shows this error ...

System.NullReferenceException: object reference not set to object instance

Stacktrace:

atPresenters.UnitTests.DeviceCategoryPresenterTest.InitializeView_Called () [0x00241] in DeviceCategoryPresenterTest.cs: 56 at (toward-wrap) System.Reflection.MonoMethod: InternalInvoke (System.Reflection.MonoMethod, & object ,., object ,. in System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object [], 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'm not sure how to fix it. Please help. Thanks in advance.

+5
source share
2 answers

The fact is that you skip your continuation by passing a valid task instead of It.IsAny<Task> . 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 continue with the TaskContinuationOptions.OnlyOnFaulted option, because you are only interested in the IsFaulted script.

Remember that you are not testing the continuation part, just skipping it. If you really want to check \ check the continuation of the part, be careful with it. It seems your logic is the service logic, so TaskScheduler will use SynchronizationContext by default and continue the schedule in the ThreadPool thread. Of course, this is done in the context of the unit test runner, which is the same. Basically, your tests can complete even before completing the continuation task.

+2
source

In your setup, you set the function to return null . You already indicated 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:

 _ServiceFacade.SynchronizeDataset(DATASET_NAME).ContinueWith(t => { if (t.IsFaulted) { //do something } }); 

... equal

 // 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 

It looks like you are writing an integration test that does not apply to your code (if your actual code does not accept non-zero as return). If so, I suggest changing the setting to something like:

 Setup(sf => sf.SynchronizeDataset(It.IsAny<string>())) .Returns(Task.CompletedTask); 
+1
source

Source: https://habr.com/ru/post/1275498/


All Articles