Why does mstest hang when awaiting in a WindowsFormsSynchronizationContext?

async-await, mstest, winforms

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);
    });
}

Problem

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?

Original source