等待永远不返回任务的TaskCompletionSource <t>

时间:2016-01-16 04:35:12

标签: c# asynchronous task taskcompletionsource

我正在尝试围绕异步pub / sub系统编写单元测试。在我的单元测试中,我创建了一个TaskCompletionSource<int>并在订阅回调中为其分配了一个值。在订阅回调中,我取消订阅这些出版物。下次我发布时,我想验证回调永远不会被击中。

[TestMethod]
[Owner("Johnathon Sullinger")]
[TestCategory("Domain")]
[TestCategory("Domain - Events")]
public async Task DomainEvents_subscription_stops_receiving_messages_after_unsubscribing()
{
    // Arrange
    string content = "Domain Test";
    var completionTask = new TaskCompletionSource<int>();

    DomainEvents.Subscribe<FakeDomainEvent>(
        (domainEvent, subscription) =>
        {
            // Set the completion source so the awaited task can fetch the result.
            completionTask.TrySetResult(1);
            subscription.Unsubscribe();
            return completionTask.Task;
        });

    // Act
    // Publish the first message
    DomainEvents.Publish(new FakeDomainEvent(content));
    await completionTask.Task;

    // Get the first result
    int firstResult = completionTask.Task.Result;

    // Publish the second message
    completionTask = new TaskCompletionSource<int>();
    DomainEvents.Publish(new FakeDomainEvent(content));
    await completionTask.Task;

    // Get the second result
    int secondResult = completionTask.Task.Result;

    // Assert
    Assert.AreEqual(1, firstResult, "The first result did not receive the expected value from the subscription delegate.");
    Assert.AreEqual(default(int), secondResult, "The second result had a value assigned to it when it shouldn't have. The unsubscription did not work.");
}

当我这样做时,测试会在第二个await处挂起。我知道这是因为Task永远不会返回。我不确定如何解决它。我知道我可以很容易地创建一个本地字段,我只需要赋值如下:

[TestMethod]
[Owner("Johnathon Sullinger")]
[TestCategory("Domain")]
[TestCategory("Domain - Events")]
public void omainEvents_subscription_stops_receiving_messages_after_unsubscribing()
{
    // Arrange
    string content = "Domain Test";
    int callbackResult = 0;

    DomainEvents.Subscribe<FakeDomainEvent>(
        (domainEvent, subscription) =>
        {
            // Set the completion source so the awaited task can fetch the result.
            callbackResult++;
            subscription.Unsubscribe();
            return Task.FromResult(callbackResult);
        });

    // Act
    // Publish the first message
    DomainEvents.Publish(new FakeDomainEvent(content));

    // Publish the second message
    DomainEvents.Publish(new FakeDomainEvent(content));

    // Assert
    Assert.AreEqual(1, firstResult, "The callback was hit more than expected, or not hit at all.");
}

但这感觉不对。这假设我从不在整个堆栈中执行await操作(我在他们是订阅者时执行)。此测试不是一个安全的测试,因为测试可以在发布完成之前完成。这里的意图是我的回调是异步的,出版物是非阻塞的后台进程。

如何在这种情况下处理CompletionSource?

1 个答案:

答案 0 :(得分:1)

很难测试不会发生的事情。关于你能做的最好的事情就是测试在合理的时间内没有发生。我有一个异步协调原语库,为了对这个场景进行单元测试,我不得不求助于在一段时间内观察任务,然后假设成功(see AssertEx.NeverCompletesAsync)。

但这不是唯一的解决方案。也许逻辑上最干净的解决方案是伪造时间本身。也就是说,如果你的系统有足够的钩子用于假时间系统,那么你实际上可以编写一个测试来确保永远不会调用回调。这听起来很奇怪,但它非常强大。缺点是需要进行大量的代码修改 - 而不仅仅是返回Task。如果您对Rx is the place to start感兴趣,TestScheduler type