Why doesn't RelayCommand RaiseCanExecuteChanged work in a unit test?
c#, mvvm, mvvm-light, relaycommand, wpf
Solution
Fixed!
nemesv was correct in that `FooCommand.RaiseCanExecuteChanged()` simply calls `CommandManager.InvalidateRequerySuggested()`.
In addition to that, `FooCommand.CanExecuteChanged` simply forwards the handler on to the `CommandManager.RequerySuggested` event:
public event EventHandler CanExecuteChanged
{
add
{
...
CommandManager.RequerySuggested += value;
}
...
}
The cause of the problem was the following line of code in the CommandManager class:
private void RaiseRequerySuggested()
{
...
_requerySuggestedOperation = dispatcher.
BeginInvoke(
DispatcherPriority.Background,
new DispatcherOperationCallback(RaiseRequerySuggested),
null); // dispatcher is the Dispatcher for the current thread.
...
}
This line places a work item with DispatcherPriority `Background` on the Dispatcher work item queue. The work item is supposed to notify all handlers of the `CommandManager.RequerySuggested` event.
The problem is that this work item is never run.
The solution is to force the dispatcher to run the work item.
I found the solution in this discussion on the MVVM Foundation CodePlex page. I managed to simplify the code somewhat into the following helper class.
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;
}
}
My test now looks like this:
// 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);
And, most importantly, it passes :)
Problem
I'm using the current version of MvvmLight available on Nuget (4.1.23.0) and calling RaiseCanExecuteChanged does not appear to be doing anything in a unit test. The scenario is very simple, I have a command: ``` public RelayCommand FooCommand { get; private set; } ``` I new it up in the view model constructor and point it to some private methods: ``` FooCommand = new RelayCommand(Foo, CanFoo); private void Foo() { // do some fooing. } private bool CanFoo() { return SomeRequiredProperty != null; } ``` Then in the setter for `SomeRequiredProperty` I call RaiseCanExecuteChanged: ``` public object SomeRequiredProperty { get { return someRequiredProperty; } set { someRequiredProperty = value; FooCommand.RaiseCanExecuteChanged(); } } ``` Now in a unit test I do the following: ``` // Arrange var canExecuteChanged = false; viewModel.FooCommand.CanExecuteChanged += (sender, args) => canExecuteChanged = true; // Act viewModel.SomeRequiredProperty = new object(); // Assert Assert.That(canExecuteChanged, Is.True); ``` The test fails because my event handler is not firing. Why is that? Update: The behaviour does indeed work at run time.