Why does RelayCommand RaiseCanExecuteChanged work in unit test?

I am using the current version of MvvmLight available in Nuget (4.1.23.0), and calling RaiseCanExecuteChanged does not seem to do anything in the unit test. The script is very simple, I have a command:

public RelayCommand FooCommand { get; private set; } 

I update it in the constructor of the view model and point out some private methods:

 FooCommand = new RelayCommand(Foo, CanFoo); private void Foo() { // do some fooing. } private bool CanFoo() { return SomeRequiredProperty != null; } 

Then in setter for SomeRequiredProperty I call RaiseCanExecuteChanged:

 public object SomeRequiredProperty { get { return someRequiredProperty; } set { someRequiredProperty = value; FooCommand.RaiseCanExecuteChanged(); } } 

Now in 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 does not start. Why is this?

Update: Behavior really works at runtime.

+8
c # wpf mvvm mvvm-light relaycommand
source share
1 answer

Fixed!

nemesv was right that FooCommand.RaiseCanExecuteChanged() just calls CommandManager.InvalidateRequerySuggested() .

In addition to this, FooCommand.CanExecuteChanged simply forwards the handler 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 the work item with DispatcherPriority Background on Dispatcher . The work item is supposed to notify all handlers of the CommandManager.RequerySuggested event.

The problem is that this work item never starts.

The solution is to force the dispatcher to run the work item.

I found a solution to this discussion on the MVVM Foundation CodePlex page. I was able to simplify the code a bit in 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 :)

+7
source share

All Articles