Search code examples
c#async-awaitnunit

How to unit test async/await example?


Having been reading some Stephen Cleary I found this post:

http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

With this example:

// My "library" method.
public static async Task<JObject> GetJsonAsync(Uri uri)
{
  using (var client = new HttpClient())
  {
    var jsonString = await client.GetStringAsync(uri);
    return JObject.Parse(jsonString);
  }
}

// My "top-level" method.
public async void Button1_Click(...)
{
  var json = await GetJsonAsync(...);
  textBox1.Text = json;
}

How would one turn this code into an NUnit unit test to demonstrate the same solution to the same problem?

I have this code as a unit test but it does not behave the same as the interface example code - unless I am misunderstanding the piece.

// My "library" method.
public static async Task<JObject> GetJsonAsync(Uri uri)
{
  using (var client = new HttpClient())
  {
    var jsonString = await client.GetStringAsync(uri);
    return JObject.Parse(jsonString);
  }
}

// My "top-level" method.
[Test]
public async void TestButton1_Click(...)
{
  var json = await GetJsonAsync(...);
  var gotOutput = json;
}

There is no assertion as that will come later.


Solution

  • There are two components to that deadlock: blocking on asynchronous code, and a single-threaded context.

    The code sample you pasted does not block on asynchronous code (no .Result). It looks like you copied the sample that does not deadlock.

    To reproduce the deadlock, you'd need to both block on the asynchronous code, and supply a single-threaded context. NUnit does have a single-threaded context that it applies in some scenarios. The details have changed a few times, but I'm pretty sure they ended up applying a context to async void methods, so I think this would deadlock:

    [Test]
    public async void TestButton1_Click(...)
    {
      var json = GetJsonAsync(...).Result;
    }
    

    You can check to see if NUnit is supplying a context by reading SynchronizationContext.Current from a breakpoint in your test. If it's not, then you can supply a context yourself like the AsyncContext in my AsyncEx library:

    [Test]
    public void TestButton1_Click(...)
    {
      var json = AsyncContext.Run(() => GetJsonAsync(...).Result);
    }