it-swarm.com.ru

Жду асинхронность 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);
}    

Я пытаюсь провести модульное тестирование так:

[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. В моем обычном коде это именно то, что я хочу. Но для моего юнит-теста я этого не хочу.

Я преобразую в Async/Await от использования BackgroundWorker. С фоновым работником это работало правильно, потому что я мог дождаться окончания работы BackgroundWorker.

Но, похоже, нет способа ждать асинхронного пустого метода ...

Как я могу модульное тестирование этого метода?

57
Vaccano

Я нашел способ сделать это для модульного тестирования:

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

Ключевым моментом здесь является то, что, поскольку я выполняю модульное тестирование, я могу заменить в задаче, которую я хочу, чтобы мой асинхронный вызов (внутри моей асинхронной пустоты) возвращался. Затем я просто проверяю, что задание выполнено, прежде чем двигаться дальше.

16
Vaccano

Вы должны избегать 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);
}

и юнит-тест это как:

[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 для вашей модели представления в течение ее срока службы.

53
Stephen Cleary

Метод async void по сути является методом «забей и забудь». Нет возможности вернуть событие завершения (без внешнего события и т.д.).

Если вам нужно выполнить модульное тестирование, я бы рекомендовал вместо этого сделать метод async Task. Затем вы можете вызвать Wait() с результатами, которые уведомят вас о завершении метода.

Однако этот метод тестирования в том виде, в котором он написан, все равно не будет работать, так как вы на самом деле не тестируете DoStuff напрямую, а скорее тестируете DelegateCommand, который оборачивает его. Вам нужно будет проверить этот метод напрямую.

50
Reed Copsey

Вы можете использовать AutoResetEvent, чтобы остановить тестовый метод до завершения асинхронного вызова:

[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
BalintN

Приведенный ответ проверяет команду, а не метод асинхронности. Как упоминалось выше, вам понадобится еще один тест, чтобы проверить этот асинхронный метод.

Потратив некоторое время на аналогичную проблему, я с легкостью подождал, чтобы протестировать асинхронный метод в модульном тесте, просто вызвав синхронно:

    protected static void CallSync(Action target)
    {
        var task = new Task(target);
        task.RunSynchronously();
    }

и использование:

CallSync(() => myClass.MyAsyncMethod());

Тест ожидает в этой строке и продолжается после того, как результат будет готов, поэтому мы можем утверждать сразу после этого.

4
Velimir

Единственный способ, который я знаю, - это превратить ваш метод async void в метод async Task

4
Alex Planchon

Измените свой метод, чтобы вернуть задачу, и вы можете использовать Task.Result

bool res = configuration.InitializeAsync(appConfig).Result;
Assert.IsTrue(res);
0
Billy Jake O'Connor