Como testar um ViewModel carregado com um BackgroundWorker?
ma das coisas boas do MVVM é a testabilidade do ViewModel. No meu caso particular, eu tenho uma VM que carrega alguns dados quando um comando é chamado e seu teste correspondente:
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);
}
Então, isso é tudo bastante simples. No entanto, o que eu realmente gostaria de fazer é carregar os dados usando umBackgroundWorker
e a mensagem 'loading' é exibida na tela. Então eu mudo a VM para:
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();
}
Isso funciona bem visualmente em tempo de execução, no entanto, meu teste agora falha devido à propriedade não ser carregada imediatamente. Alguém pode sugerir uma boa maneira de testar esse tipo de carregamento? Suponho que o que preciso seja 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.");
}
No entanto, isso parece realmente desajeitado ... Funciona, mas parece sujo. Alguma sugestão melhor?
Eventual Solution:
Com base na resposta de David Hall, para zombar de um BackgroundWorker, acabei fazendo esse invólucro bastante simples em torno deBackgroundWorker
que define duas classes, uma que carrega dados de forma assíncrona e outra que carrega de forma síncron
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));
}
}
}
Então, na minha configuração do Unity, posso usar o SyncWorker para teste e o AsyncWorker para produção. Meu ViewModel passa a ser:
public class MyViewModel(IWorker bgWorker)
{
public void OnLoadData()
{
IsBusy = true;
bgWorker.Run(
(sender, e) =>
{
MyData = wcfClient.LoadData();
},
(sender, e) =>
{
IsBusy = false;
});
}
}
Observe que a coisa que eu marquei comowcfClient
também é uma simulação nos meus testes, portanto, após a chamada paravm.LoadDataCommand.Execute()
Eu também posso validar quewcfClient.LoadData()
foi chamado