¿Cómo probar un ViewModel que se carga con un BackgroundWorker?
Una de las cosas buenas de MVVM es la capacidad de prueba de ViewModel. En mi caso particular, tengo una VM que carga algunos datos cuando se llama a un comando, y su prueba correspondiente:
public class MyViewModel
{
public DelegateCommand LoadDataCommand { get; set; }
private List<Data> myData;
public List<Data> MyData
{
get { return myData; }
set { myData = value; RaisePropertyChanged(() => MyData); }
}
public MyViewModel()
{
LoadDataCommand = new DelegateCommand(OnLoadData);
}
private void OnLoadData()
{
// loads data over wcf or db or whatever. doesn't matter from where...
MyData = wcfClient.LoadData();
}
}
[TestMethod]
public void LoadDataTest()
{
var vm = new MyViewModel();
vm.LoadDataCommand.Execute();
Assert.IsNotNull(vm.MyData);
}
Así que todo eso es bastante simple. Sin embargo, lo que realmente me gustaría hacer es cargar los datos usando unaBackgroundWorker
, y aparece un mensaje de "cargando" en la pantalla. Entonces cambio la VM a:
private void OnLoadData()
{
IsBusy = true; // view is bound to IsBusy to show 'loading' message.
var bg = new BackgroundWorker();
bg.DoWork += (sender, e) =>
{
MyData = wcfClient.LoadData();
};
bg.RunWorkerCompleted += (sender, e) =>
{
IsBusy = false;
};
bg.RunWorkerAsync();
}
Esto funciona bien visualmente en tiempo de ejecución, sin embargo, mi prueba ahora falla debido a que la propiedad no se carga de inmediato. ¿Alguien puede sugerir una buena manera de probar este tipo de carga? Supongo que lo que necesito es algo como:
[TestMethod]
public void LoadDataTest()
{
var vm = new MyViewModel();
vm.LoadDataCommand.Execute();
// wait a while and see if the data gets loaded.
for(int i = 0; i < 10; i++)
{
Thread.Sleep(100);
if(vm.MyData != null)
return; // success
}
Assert.Fail("Data not loaded in a reasonable time.");
}
Sin embargo, eso parece realmente torpe ... Funciona, pero se siente sucio. ¿Alguna sugerencia mejor?
Solución Eventual:
Basado en la respuesta de David Hall, para burlarme de un BackgroundWorker, terminé haciendo este envoltorio bastante simple alrededor deBackgroundWorker
que define dos clases, una que carga datos de forma asincrónica y otra que carga de forma sincrónica.
public interface IWorker
{
void Run(DoWorkEventHandler doWork);
void Run(DoWorkEventHandler doWork, RunWorkerCompletedEventHandler onComplete);
}
public class AsyncWorker : IWorker
{
public void Run(DoWorkEventHandler doWork)
{
Run(doWork, null);
}
public void Run(DoWorkEventHandler doWork, RunWorkerCompletedEventHandler onComplete)
{
var bg = new BackgroundWorker();
bg.DoWork += doWork;
if(onComplete != null)
bg.RunWorkerCompleted += onComplete;
bg.RunWorkerAsync();
}
}
public class SyncWorker : IWorker
{
public void Run(DoWorkEventHandler doWork)
{
Run(doWork, null);
}
public void Run(DoWorkEventHandler doWork, RunWorkerCompletedEventHandler onComplete)
{
Exception error = null;
var args = new DoWorkEventArgs(null);
try
{
doWork(this, args);
}
catch (Exception ex)
{
error = ex;
throw;
}
finally
{
onComplete(this, new RunWorkerCompletedEventArgs(args.Result, error, args.Cancel));
}
}
}
ntonces, en mi configuración de Unity, puedo usar SyncWorker para las pruebas y AsyncWorker para la producción. Mi ViewModel se convierte en:
public class MyViewModel(IWorker bgWorker)
{
public void OnLoadData()
{
IsBusy = true;
bgWorker.Run(
(sender, e) =>
{
MyData = wcfClient.LoadData();
},
(sender, e) =>
{
IsBusy = false;
});
}
}
enga en cuenta que lo que he marcado comowcfClient
es en realidad un simulacro en mis pruebas también, así que después de la llamada avm.LoadDataCommand.Execute()
También puedo validar quewcfClient.LoadData()
fue llamado.