unitarias - testing in c# visual studio
¿Por qué RelayCommand RaiseCanExecuteChanged no funciona en una prueba unitaria? (1)
Estoy usando la versión actual de MvvmLight disponible en Nuget (4.1.23.0) y llamar a RaiseCanExecuteChanged no parece estar haciendo nada en una prueba unitaria. El escenario es muy simple, tengo un comando:
public RelayCommand FooCommand { get; private set; }
Lo nuevo en el constructor del modelo de vista y lo apunto a algunos métodos privados:
FooCommand = new RelayCommand(Foo, CanFoo);
private void Foo()
{
// do some fooing.
}
private bool CanFoo()
{
return SomeRequiredProperty != null;
}
Luego en el setter para SomeRequiredProperty
llamo a RaiseCanExecuteChanged:
public object SomeRequiredProperty
{
get
{
return someRequiredProperty;
}
set
{
someRequiredProperty = value;
FooCommand.RaiseCanExecuteChanged();
}
}
Ahora en una prueba unitaria hago lo siguiente:
// Arrange
var canExecuteChanged = false;
viewModel.FooCommand.CanExecuteChanged += (sender, args) => canExecuteChanged = true;
// Act
viewModel.SomeRequiredProperty = new object();
// Assert
Assert.That(canExecuteChanged, Is.True);
La prueba falla porque mi controlador de eventos no está disparando. ¿Porqué es eso?
Actualización: el comportamiento realmente funciona en tiempo de ejecución.
¡Fijo!
nemesv tenía razón en que FooCommand.RaiseCanExecuteChanged()
simplemente llama a CommandManager.InvalidateRequerySuggested()
.
Además de eso, FooCommand.CanExecuteChanged
simplemente reenvía el controlador al evento CommandManager.RequerySuggested
:
public event EventHandler CanExecuteChanged
{
add
{
...
CommandManager.RequerySuggested += value;
}
...
}
La causa del problema fue la siguiente línea de código en la clase CommandManager :
private void RaiseRequerySuggested()
{
...
_requerySuggestedOperation = dispatcher.
BeginInvoke(
DispatcherPriority.Background,
new DispatcherOperationCallback(RaiseRequerySuggested),
null); // dispatcher is the Dispatcher for the current thread.
...
}
Esta línea coloca un elemento de trabajo con DispatcherPriority Background
en la cola de elementos de trabajo de Dispatcher . Se supone que el elemento de trabajo notifica a todos los controladores del evento CommandManager.RequerySuggested
.
El problema es que este elemento de trabajo nunca se ejecuta.
La solución es forzar al despachador a ejecutar el elemento de trabajo.
Encontré la solución en esta discusión en la página MVVM Foundation CodePlex. Pude simplificar un poco el código en la siguiente clase de ayuda.
public static class DispatcherTestHelper
{
private static DispatcherOperationCallback exitFrameCallback = ExitFrame;
/// <summary>
/// Synchronously processes all work items in the current dispatcher queue.
/// </summary>
/// <param name="minimumPriority">
/// The minimum priority.
/// All work items of equal or higher priority will be processed.
/// </param>
public static void ProcessWorkItems(DispatcherPriority minimumPriority)
{
var frame = new DispatcherFrame();
// Queue a work item.
Dispatcher.CurrentDispatcher.BeginInvoke(
minimumPriority, exitFrameCallback, frame);
// Force the work item to run.
// All queued work items of equal or higher priority will be run first.
Dispatcher.PushFrame(frame);
}
private static object ExitFrame(object state)
{
var frame = (DispatcherFrame)state;
// Stops processing of work items, causing PushFrame to return.
frame.Continue = false;
return null;
}
}
Mi prueba ahora se ve así:
// Arrange
var canExecuteChanged = false;
viewModel.FooCommand.CanExecuteChanged +=
(sender, args) => canExecuteChanged = true;
// Act
viewModel.SomeRequiredProperty = new object();
DispatcherTestHelper.ProcessWorkItems(DispatcherPriority.Background);
// Assert
Assert.That(canExecuteChanged, Is.True);
Y, lo más importante, pasa :)