I have a problem with testing an ICommand
scenario in a Xamarin project. I have extracted the logic into a demonstration below.
Scenario N1 runs smoothly however I need Scenario N2 to work. The problem with scenario N2 is that as soon as it gets to the
await Task.Run(() => Task.Delay(1000));
it jumps back to the test method Assert
where obviously the SetSurveyContext(int x)
is not executed yet.
The strangest thing is that if I run this code from the Xamarin framework inside the app everything works fine probably because I am executing the Command in a wrong manner.
Really stuck with this question, I have tried numerous ways to run the command but neither have worked. Please help if someone has come across the same problem. Thanks.
Scenario 1 - working
[Test]
public async Task NewSurvey_SendObjectWithOnlyDate_StaticSurveyResourceIdAndDateSet()
{
var mvmTest = new TesterPage();
await mvmTest.NewSurvey();
Assert.That(mvmTest.setter, Is.EqualTo(3));
}
public partial class TesterPage : ContentPage
{
public int setter = 0;
public TesterPage()
{
InitializeComponent ();
}
public async Task NewSurvey()
{
await PostNewSurvey();
}
private async Task PostNewSurvey()
{
var response = await Another();
SetSurveyContext(response);
}
private async Task<int> Another()
{
await Task.Run(() => Task.Delay(1000));
return 3;
}
private void SetSurveyContext(int x)
{
setter = x;
}
}
Test green, everything runs smoothly.
Scenario N2 - fails
[Test]
public async Task NewSurvey_SendObjectWithOnlyDate_StaticSurveyResourceIdAndDateSet()
{
var mvmTest = new TesterPage();
mvmTest.NewSurveyCommand.Execute(null);
Assert.That(mvmTest.setter, Is.EqualTo(3));
}
public partial class TesterPage : ContentPage
{
public int setter = 0;
public TesterPage ()
{
InitializeComponent ();
NewSurveyCommand = new Command(async () => await NewSurvey());
}
public ICommand NewSurveyCommand { get; private set; }
public async Task NewSurvey()
{
await PostNewSurvey();
}
private async Task PostNewSurvey()
{
var response = await Another();
SetSurveyContext(response);
}
private async Task<int> Another()
{
await Task.Run(() => Task.Delay(1000));
return 3;
}
private void SetSurveyContext(int x)
{
setter = x;
}
}
Because I have solved this problem with @YuriZolotarev in chat, here the solution we found for everyone else who encounters it:
The problem:
When Task.Run()
is called in TesterPage.Another()
the main thread jumps back to the test method. There it executes Assert.That()
immediately, even before SetSurveyContext
has set setter
to3
.
The solution:
We found the solution to create a new private field in TesterPage
which should contain the Task
started in TesterPage.Another()
. This Task
can be waited for in the test method via Reflection. Everything could look like this:
[Test]
public async Task NewSurvey_SendObjectWithOnlyDate_StaticSurveyResourceIdAndDateSet()
{
var mvmTest = new TesterPage();
mvmTest.NewSurveyCommand.Execute(null);
// Use Reflection to wait for the task
(GetInstanceField(typeof(TesterPage), mvmTest, "runningTask") as Task).Wait();
Assert.That(mvmTest.setter, Is.EqualTo(3));
}
// A helper method to simplify Reflection
internal static object GetInstanceField(Type type, object instance, string fieldName)
{
BindingFlags bindFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
| BindingFlags.Static;
FieldInfo field = type.GetField(fieldName, bindFlags);
return field.GetValue(instance);
}
public partial class TesterPage : ContentPage
{
public int setter = 0;
private Task runningTask; // The field our Task object is saved in
public TesterPage ()
{
InitializeComponent ();
NewSurveyCommand = new Command(async () => await (runningTask = NewSurvey()));
}
public ICommand NewSurveyCommand { get; private set; }
public async Task NewSurvey()
{
await PostNewSurvey();
}
private async Task PostNewSurvey()
{
var response = await Another();
SetSurveyContext(response);
}
private async Task<int> Another()
{
await Task.Run(() => Task.Delay(1000));
return 3;
}
private void SetSurveyContext(int x)
{
setter = x;
}
}