Ожидание TaskCompletionSource<T>, которое никогда не возвращает задачу

Я пытаюсь написать модульный тест вокруг асинхронного паба / подсистемы. В моем модульном тесте я создаю 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, Я понимаю, что это происходит из-за того, что Задание никогда не возвращается. Что я не уверен, так это как обойти это. Я знаю, что могу легко создать локальное поле, которому я просто присваиваю значения следующим образом:

[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.");
}

Это чувствует себя неправильно, хотя. Это предполагает, что я никогда не выполняю операцию ожидания (что я делаю, когда их подписчики) во всем стеке. Этот тест не является безопасным, так как тест может закончиться до того, как публикация будет полностью завершена. Намерение здесь состоит в том, что мои обратные вызовы являются асинхронными, а публикации - неблокирующими фоновыми процессами.

Как мне обработать CompletionSource в этом сценарии?

1 ответ

Трудно проверить, что что-то никогда не случится. Самое лучшее, что вы можете сделать, это проверить, что это не произошло в разумные сроки. У меня есть библиотека примитивов асинхронной координации, и для модульного тестирования этого сценария мне пришлось прибегнуть к взлому: наблюдать за заданием только в течение определенного периода времени, а затем предполагать успех ( см.AssertEx.NeverCompletesAsync).

Это не единственное решение, хотя. Возможно, самое логичное решение - подделать само время. То есть, если ваша система имеет достаточно хуков для системы с поддельным временем, вы можете написать тест, гарантирующий, что обратный вызов никогда не будет вызван. Это звучит очень странно, но довольно мощно. Недостатком является то, что это потребует значительных изменений кода - гораздо больше, чем просто возврат Task, Если вы заинтересованы, Rx это место, с которого можно начатьTestScheduler тип.

Другие вопросы по тегам