2

My Paste command seems to work during normal execution, but in unit test the CanExecute method always returns false.

Code:

public class ViewModel
{
    public CommandBindingCollection CommandBindings { get; set; }
    public ICommand PasteCommand { get; set; }

    public ViewModel()
    {
        CommandBinding pasteBinding 
            = new CommandBinding(ApplicationCommands.Paste, Paste, CanPasteExecute);
        RegisterCommandBinding(pasteBinding, typeof(ViewModel));
        PasteCommand = (RoutedUICommand)pasteBinding.Command;
    }

    private void CanPasteExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = true;
    }
    private void Paste(object sender, ExecutedRoutedEventArgs e)
    {
        // ...
    }
    private void RegisterCommandBinding(CommandBinding newCommandBinding, Type type)
    {
        if (CommandBindings == null)
            CommandBindings = new CommandBindingCollection();
        CommandManager.RegisterClassCommandBinding(type, newCommandBinding);
        CommandBindings.Add(newCommandBinding);
    }
}

Unit test:

[TestClass]
public class ViewModelTests
{
    private ViewModel _viewModel;

    [TestInitialize]
    public void Initialise()
    {
        _viewModel = new ViewModel();
    }

    [TestMethod]
    public void PasteTest()
    {
        // canExecute is always false somehow
        bool canExecute = _viewModel.PasteCommand.CanExecute(null);
        Assert.AreEqual<bool>(true, canExecute);
    }
}
Phil Gan
  • 2,813
  • 2
  • 29
  • 38

2 Answers2

4

I'm guessing that you bind your CommandBindings property to a UI control at some point, and that the command is fired from the UI?

RoutedCommands like ApplicationCommands.Paste rely on there being a CommandBinding at a parent UI element, above that which the command is fired on. The command's CanExucute request starts at the control on which the command is invoked (either the current focus or the command's target), and bubbles upward like a RoutedEvent looking for a matching CommandBinding. When it finds one, it executes the CanExecute delegate from the binding to return the value you're looking for.

Because there's no UI in your test, and no target for the command, calling the command's CanExecute will simply not find a delegate and will thus return false.

So, I don't think your test in its current form would work without the UI being present.

(I'm going to go test my theory now - will edit later!)

Dan Puzey
  • 33,626
  • 4
  • 73
  • 96
  • Thanks, I look forward to it. – Phil Gan Apr 01 '11 at 09:38
  • Hi Phil - looks like I am correct, from my testing. Do you bind something in your UI to the `CommandBindings` property of your view? – Dan Puzey Apr 01 '11 at 11:02
  • Yes, I do. I've exposed public methods on my ViewModel that do the work and I'm calling those in my unit tests. It seems a bit dirty, but it's better than no unit testing. – Phil Gan Apr 01 '11 at 11:39
  • Just wondering if you ever found a better way for testing these types of methods that doesn't break encapsulation @PhilGan – stackunderflow Nov 22 '13 at 23:40
0

This is an old question, but I've found that this is still a problem.

After reading Dan Puzey's answer, and searching down-down-down into what CanExecute needs to run properly, I adapted my unit tests to add a dummy UI Element to attach to:

var aButton = new Button();
aButton.CommandBindings.Add(command);
            
Assert.That((command.Command as RoutedUICommand).CanExecute(null, 
aButton), Is.EqualTo(canExecute));

if (canExecute)
{
    (command.Command as RoutedUICommand).Execute(null, aButton);
    //verify Execute results
}

This keeps encapsulation, while allowing the results of both CanExecute and Execute to be checked. Note that, as far as I can tell, a call to Execute still also calls CanExecute, so you can't check the results of just Execute without forcing CanExecute to return true in some way.

It doesn't have to be a button, any UI Element that can bind a command will do.

clnoel
  • 22
  • 5