Ожидание 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
тип.