Question

I have a custom Windows Forms control that is covered with MSTest unit tests. I added an async method to this class that I need to cover as well but the new test always times out.

After some experimentation I've reduced the problem down to just a control creation and an await.

[TestMethod]
public async Task TestMethod1()
{
    Control c = new Control();
    await Task.Delay(1);
}

I noticed that the test begins with the SynchronizationContext.Current set to null, then as soon as the control is created it changes to a WindowsFormsSynchronizationContext. If I force the synchronization context back to null, then the test will pass.

[TestMethod]
public async Task TestMethod1()
{
    Control c = new Control();
    SynchronizationContext.SetSynchronizationContext(null);
    await Task.Delay(1);
}

Is there any way to use await from a WindowsFormsSynchronizationContext without blocking?

Was it helpful?

Solution

Windows Forms controls assume that they are running inside a Windows Forms application, which is not the case for unit tests. That is why your unit test is hanging.

To be more specific, WinForms controls install a WindowsFormsSynchronizationContext that delegates work to the Win32 message loop inside Application.Run. So, your async method sees the context and queues its continuation to that message loop. However, there is no actual message loop because your unit test doesn't ever call Application.Run.

The best solution IMO is to use an MVVM pattern where you do not test UI elements outside of a UI application, so you never run into this situation (in MVVM, you unit test the logical UI but not the literal UI). With an MVVM approach your code would look like this:

[TestMethod]
public async Task TestMethod1()
{
  ViewModel vm = new ViewModel();
  await vm.MethodAsync();
}

But if you do want to unit test the actual UI elements, you can use a type like the Windows Forms Context that was included in the Async CTP:

[TestMethod]
public async Task TestMethod1()
{
    await WindowsFormsContext.Run(async () =>
    {
        Control c = new Control();
        await Task.Delay(1);
    });
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top