Ожидание вызова метода Async Void для модульного тестирования
У меня есть метод, который выглядит так:
private async void DoStuff(long idToLookUp)
{
IOrder order = await orderService.LookUpIdAsync(idToLookUp);
// Close the search
IsSearchShowing = false;
}
//Other stuff in case you want to see it
public DelegateCommand<long> DoLookupCommand{ get; set; }
ViewModel()
{
DoLookupCommand= new DelegateCommand<long>(DoStuff);
}
Я пытаюсь выполнить unit test так:
[TestMethod]
public void TestDoStuff()
{
//+ Arrange
myViewModel.IsSearchShowing = true;
// container is my Unity container and it setup in the init method.
container.Resolve<IOrderService>().Returns(orderService);
orderService = Substitute.For<IOrderService>();
orderService.LookUpIdAsync(Arg.Any<long>())
.Returns(new Task<IOrder>(() => null));
//+ Act
myViewModel.DoLookupCommand.Execute(0);
//+ Assert
myViewModel.IsSearchShowing.Should().BeFalse();
}
Мое утверждение вызывается до того, как я закончу с издеваемым LookUpIdAsync. В моем нормальном коде это то, что я хочу. Но для моего unit test я этого не хочу.
Я перехожу к Async/Await с помощью BackgroundWorker. С фоновым рабочим это работало правильно, потому что я мог дождаться завершения BackgroundWorker.
Но, похоже, не существует способа ждать метода async void...
Как я могу unit test использовать этот метод?
Ответы
Ответ 1
Я понял способ сделать это для модульного тестирования:
[TestMethod]
public void TestDoStuff()
{
//+ Arrange
myViewModel.IsSearchShowing = true;
// container is my Unity container and it setup in the init method.
container.Resolve<IOrderService>().Returns(orderService);
orderService = Substitute.For<IOrderService>();
var lookupTask = Task<IOrder>.Factory.StartNew(() =>
{
return new Order();
});
orderService.LookUpIdAsync(Arg.Any<long>()).Returns(lookupTask);
//+ Act
myViewModel.DoLookupCommand.Execute(0);
lookupTask.Wait();
//+ Assert
myViewModel.IsSearchShowing.Should().BeFalse();
}
Ключевым моментом здесь является то, что, поскольку я являюсь модульным тестированием, я могу заменить в задаче, я хочу, чтобы мой асинхронный вызов (внутри моего асинхронного void) возвращался. Затем я просто выполняю задачу, прежде чем двигаться дальше.
Ответ 2
Вам следует избегать async void
. Используйте только async void
для обработчиков событий. DelegateCommand
(логически) обработчик события, поэтому вы можете сделать это следующим образом:
// Use [InternalsVisibleTo] to share internal methods with the unit test project.
internal async Task DoLookupCommandImpl(long idToLookUp)
{
IOrder order = await orderService.LookUpIdAsync(idToLookUp);
// Close the search
IsSearchShowing = false;
}
private async void DoStuff(long idToLookUp)
{
await DoLookupCommandImpl(idToLookup);
}
и unit test это как:
[TestMethod]
public async Task TestDoStuff()
{
//+ Arrange
myViewModel.IsSearchShowing = true;
// container is my Unity container and it setup in the init method.
container.Resolve<IOrderService>().Returns(orderService);
orderService = Substitute.For<IOrderService>();
orderService.LookUpIdAsync(Arg.Any<long>())
.Returns(new Task<IOrder>(() => null));
//+ Act
await myViewModel.DoLookupCommandImpl(0);
//+ Assert
myViewModel.IsSearchShowing.Should().BeFalse();
}
Мой рекомендуемый ответ выше. Но если вы действительно хотите протестировать метод async void
, вы можете сделать это с помощью библиотеки AsyncEx:
[TestMethod]
public void TestDoStuff()
{
AsyncContext.Run(() =>
{
//+ Arrange
myViewModel.IsSearchShowing = true;
// container is my Unity container and it setup in the init method.
container.Resolve<IOrderService>().Returns(orderService);
orderService = Substitute.For<IOrderService>();
orderService.LookUpIdAsync(Arg.Any<long>())
.Returns(new Task<IOrder>(() => null));
//+ Act
myViewModel.DoLookupCommand.Execute(0);
});
//+ Assert
myViewModel.IsSearchShowing.Should().BeFalse();
}
Но это решение изменяет SynchronizationContext
для вашей модели просмотра в течение ее жизни.
Ответ 3
Метод async void
по существу является методом "огонь и забвение". Нет средств для возврата события завершения (без внешнего события и т.д.).
Если вам нужно unit test, я бы рекомендовал вместо него использовать метод async Task
. Затем вы можете вызывать Wait()
по результатам, которые будут уведомлять вас о завершении метода.
Однако этот тестовый метод, как написано, все равно не будет работать, поскольку вы фактически не тестируете DoStuff
напрямую, а скорее проверяете DelegateCommand
, который его обертывает. Вам нужно будет протестировать этот метод напрямую.
Ответ 4
Вы можете использовать AutoResetEvent, чтобы остановить метод тестирования, пока не завершится вызов async:
[TestMethod()]
public void Async_Test()
{
TypeToTest target = new TypeToTest();
AutoResetEvent AsyncCallComplete = new AutoResetEvent(false);
SuccessResponse SuccessResult = null;
Exception FailureResult = null;
target.AsyncMethodToTest(
(SuccessResponse response) =>
{
SuccessResult = response;
AsyncCallComplete.Set();
},
(Exception ex) =>
{
FailureResult = ex;
AsyncCallComplete.Set();
}
);
// Wait until either async results signal completion.
AsyncCallComplete.WaitOne();
Assert.AreEqual(null, FailureResult);
}
Ответ 5
Единственный способ, который я знаю, - это превратить ваш async void
метод async void
в async Task
метод async Task
Ответ 6
Предоставленный ответ проверяет команду, а не метод async. Как упомянуто выше, вам понадобится еще один тест для тестирования этого асинхронного метода.
Проведя некоторое время с аналогичной проблемой, я нашел легкое ожидание, чтобы протестировать метод асинхронизации в unit test, просто позвонив синхронно:
protected static void CallSync(Action target)
{
var task = new Task(target);
task.RunSynchronously();
}
и использование:
CallSync(() => myClass.MyAsyncMethod());
Тест ожидает этой строки и продолжается после того, как результат будет готов, поэтому мы сразу можем утверждать.
Ответ 7
Измените свой метод для возврата Задачи, и вы можете использовать Task.Result
bool res = configuration.InitializeAsync(appConfig).Result;
Assert.IsTrue(res);
Ответ 8
У меня была похожая проблема. В моем случае решение состояло в том, чтобы использовать Task.FromResult
в настройке moq для .Returns(...)
следующим образом:
orderService.LookUpIdAsync(Arg.Any<long>())
.Returns(Task.FromResult(null));
В качестве альтернативы, Moq также имеет метод ReturnsAysnc(...)
.