如何单元测试异步/等待示例?

时间:2017-06-01 09:20:15

标签: async-await nunit

在阅读了一些Stephen Cleary后,我发现了这篇文章:

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

通过这个例子:

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

如何将此代码转换为NUnit单元测试以演示针对同一问题的相同解决方案?

我将此代码作为单元测试,但它的行为与接口示例代码的行为不同 - 除非我误解了该部分。

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

没有断言,因为这会在以后发生。

2 个答案:

答案 0 :(得分:4)

该死锁有两个组件:阻塞异步代码和单线程上下文。

您粘贴的代码示例不会阻止异步代码(无.Result)。看起来你复制了死锁的样本。

要重现死锁,您需要同时阻止异步代码,并提供单线程上下文。 NUnit确实具有在某些情况下应用的单线程上下文。细节已经改变了几次,但我很确定他们最终应用了async void方法的上下文,所以我认为这会陷入僵局:

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

您可以通过从测试中的断点读取SynchronizationContext.Current来检查NUnit是否正在提供上下文。如果不是,那么您可以自己提供上下文,例如AsyncContext in my AsyncEx library

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

答案 1 :(得分:0)

我无法在XUnit 2.4.0中编写一个测试,该测试重现了以上斯蒂芬的模式所产生的僵局。

我最终要做的是将一个动作注入到一个模拟组件中,该组件断言异步代码没有上下文:

mock.ValidationAction = () => Assert.Null(SynchronizationContext.Current);

确实是在同步运行时填充了上下文,但是当异步部分在线程池中完成时(我的代码层称为Task.Run(...)),上下文为空。

我认为这也许可以证明代码至少在该组件和代码路径中不会陷入死锁。