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.

Original source