Как выполнить юнит-тест BeginInvoke на действии
Я ищу способ протестировать BeginInvoke в методе Action, поскольку метод работает в фоновом потоке, поэтому нет способа узнать, когда он фактически завершается или вызывает метод обратного вызова. Я ищу способ подождать, пока мой тест не будет вызван до обратного вызова.
В следующем классе Presenter вы можете заметить, что я вызываю PopulateView в фоновом потоке, который обновляет представление при получении данных, и я пытаюсь утверждать, что свойства представления правильно инициализированы.
Я использую NUnit и Moq.
public class Presenter
{
private IView _view;
private IService _service;
public Presenter(IView view, IService service)
{
_view = view;
_service = service;
Action action = PopulateView;
action.BeginInvoke(PopulateViewCallback, action);
}
private void PopulateViewCallback(IAsyncResult ar)
{
try
{
Action target = (Action)ar.AsyncState;
target.EndInvoke(ar);
}
catch (Exception ex)
{
Logger.Instance.LogException("Failed to initialize view", ex);
}
}
private void PopulateView()
{
Thread.Sleep(2000); // Fetch data _service.DoSomeThing()
_view.Property1 = "xyz";
}
}
3 ответа
Абстрагируйте ваш код, чтобы вы могли внедрить то поведение, которое вы хотите во время тестирования
public class MethodInvoker
{
public virtual void InvokeMethod(Action method, Action callback)
{
method.BeginInvoke(callback, method);
}
}
Эта версия является асинхронной. Во время тестирования вы можете просто сделать блокирующую версию:
public class TestInvoker
{
public IAsyncResult MockResult { get; set; }
public override void InvokeMethod(Action method, Action callback)
{
method();
callback(MockResult);
}
}
Тогда ваш код просто меняется на это:
// Inject this dependency
Invoker.InvokeMethod(PopulateView, PopulateViewCallback);
Во время выполнения это асинхронно. Во время тестирования он блокирует вызов.
BeginInvoke()
возвращает IAsyncResult
который вы можете использовать, чтобы ждать.
IAsynchResult ar = action.BeginInvoke(...);
ar.AsyncWaitHandle.WaitOne();
Вам не нужно проверять, что методы вызываются, а тестировать конечный результат - в этом случае это _view.Propert1 == "xyz".
Поскольку это асинхронный вызов, вам может понадобиться цикл, который периодически утверждает, что значение было установлено, а также тайм-аут в тесте или проверка является обязательным, иначе ваш тест никогда не будет неудачным (просто застрянет).
Вы можете подумать о заглушении действия (PopulateView), чтобы пропустить Sleep.