7

今天我在使用异步 ApiControllers 创建 Web API 时遇到了问题。我正在使用 MongoDB,由于 C# 驱动程序不支持异步,我尝试在我的存储库层中实现它。

Building 存储库中生成的方法如下所示:

public async Task<IEnumerable<Building>> GetAll()
{
    var tcs = new TaskCompletetionSource<IEnumerable<Building>>();

    await Task.Run(() => {
        var c = this.MongoDbCollection.FindAll();
        tcs.SetResult(c);
    });

    return await tcs.Task;
}

现在,当使用 NUnit 自行测试存储库时,这非常有效。

但是当从控制器(使用 HttpClient)进行测试时,它永远不会在运行后进入“返回”行tcs.SetResult(c)。测试一直运行,直到我手动中止它。

当我删除 Task.Run 代码并同步执行所有操作时,一切正常:

public async Task<IEnumerable<Building>> GetAll()
{
    var c = this.MongoDbCollection.FindAll();

    return c;
}

有谁知道为什么我在测试存储库 + 数据库和测试控制器 + 存储库 + 数据库时会遇到不同的行为?

控制器方法如下所示:(buildingRepository使用 Ninject 在构造函数中注入)

public async Task<HttpResponseMessage> Get()
{
    var result = await this.buildingRepository.GetAll();
    return Request.CreateResponse(HttpStatusCode.OK, result);
}

编辑:这里也是测试方法。第一个是不工作的:(this.client是一个 HttpClient 对象,其 accept-header 设置为"application/json"

[Test]
public void Get_WhenBuildingsExist_ShouldReturnBuilding()
{
    var task = this.client.GetAsync("/api/building/");

    var result = task.Result.Content.ReadAsStringAsync().Result;

    var o = JsonConvert.DeserializeObject<IEnumerable<Building>>(result);

    Assert.That(o.Any());
}

[Test]
public void Get_WhenBuildingsExist_ShouldReturnAtLeastOneBuilding()
{
    var buildings = this.buildingRepository.GetAll().Result;

    Assert.That(buildings.Any());
}
4

2 回答 2

3

我读过一篇文章,解释了为什么.Results从异步任务调用是一个坏主意,但我目前没有它可用。基本上,您这样做是在扼杀异步处理。尝试如下更改您的测试:

[Test]
public void Get_WhenBuildingsExist_ShouldReturnBuilding()
{
    var task = this.client.GetAsync("/api/building/");

    var resultTask = task.Result.Content.ReadAsStringAsync();
    resultTask.Wait();

    var result = resultTask.Result;
    var o = JsonConvert.DeserializeObject<IEnumerable<Building>>(result);

    Assert.That(o.Any());
}
于 2012-08-14T14:08:46.740 回答
2

最好的选择是升级到支持异步单元测试的 NUnit 版本,并将所有Result/Wait调用更改为await

[Test]
public async Task Get_WhenBuildingsExist_ShouldReturnBuilding()
{
  var content = await this.client.GetAsync("/api/building/");
  var result = await content.ReadAsStringAsync();
  var o = JsonConvert.DeserializeObject<IEnumerable<Building>>(result);

  Assert.That(o.Any());
}

如果这是不可能的(例如,在撰写本文时,Xamarin 仍在运行非常旧版本的 NUnit),那么您可以AsyncContext从我的 AsyncEx 库中使用:

[Test]
public void Get_WhenBuildingsExist_ShouldReturnBuilding()
{
  var o = AsyncContext.Run(async () =>
  {
    var content = await this.client.GetAsync("/api/building/");
    var result = await content.ReadAsStringAsync();
    return JsonConvert.DeserializeObject<IEnumerable<Building>>(result);
  });

  Assert.That(o.Any());
}
于 2014-03-25T12:23:36.950 回答