Подтвердить что ты не робот

Ожидание вызова метода 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 использовать этот метод?

4b9b3361

Ответ 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(...).