views:

307

answers:

2

I am still grokking attached behaviors in general, and am at a loss to see how to write a unit test for one.

I pasted some code below from Sacha Barber's Cinch framework that allows a window to be closed via attached behavior. Can somewone show me an example unit test for it?

Thanks!
Berryl

    #region Close

    /// <summary>Dependency property which holds the ICommand for the Close event</summary>
    public static readonly DependencyProperty CloseProperty =
        DependencyProperty.RegisterAttached("Close",
            typeof(ICommand), typeof(Lifetime),
                new UIPropertyMetadata(null, OnCloseEventInfoChanged));

    /// <summary>Attached Property getter to retrieve the CloseProperty ICommand</summary>
    public static ICommand GetClose(DependencyObject source)
    {
        return (ICommand)source.GetValue(CloseProperty);
    }

    /// <summary>Attached Property setter to change the CloseProperty ICommand</summary>
    public static void SetClose(DependencyObject source, ICommand command)
    {
        source.SetValue(CloseProperty, command);
    }

    /// <summary>This is the property changed handler for the Close property.</summary>
    private static void OnCloseEventInfoChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var win = sender as Window;
        if (win == null) return;

        win.Closing -= OnWindowClosing;
        win.Closed -= OnWindowClosed;

        if (e.NewValue == null) return;

        win.Closing += OnWindowClosing;
        win.Closed += OnWindowClosed;
    }

    /// <summary>
    /// This method is invoked when the Window.Closing event is raised.  
    /// It checks with the ICommand.CanExecute handler
    /// and cancels the event if the handler returns false.
    /// </summary>
    private static void OnWindowClosing(object sender, CancelEventArgs e)
    {
        var dpo = (DependencyObject)sender;
        var ic = GetClose(dpo);
        if (ic == null) return;

        e.Cancel = !ic.CanExecute(GetCommandParameter(dpo));
    }

    /// <summary>
    /// This method is invoked when the Window.Closed event is raised.  
    /// It executes the ICommand.Execute handler.
    /// </summary>
    static void OnWindowClosed(object sender, EventArgs e)
    {
        var dpo = (DependencyObject)sender;
        var ic = GetClose(dpo);
        if (ic == null) return;

        ic.Execute(GetCommandParameter(dpo));
    }

    #endregion
+1  A: 

Hi Berryl,

DependencyProperty changing and value coercion on their own looks like 'Impossible Dependencies' for me. Having reference to Window there makes things even trickier. I think I'd go with Humble Object pattern here...

Anvaka
I can actually live with the window in this case, but that is an interesting link. Can you illustrate how you would apply the Humble Object Pattern in this case? Cheers
Berryl
+2  A: 

You would likely use a lambda in your ICommand using a DelegateCommand or a RelayCommand. Multiple implementations of these exists all over the place and Cinch may have something similar. Really simple version (as an example, not meant for production use):

public class DelegateCommand : ICommand {
    private Action _execute = null;

    public void Execute( object parameter ) {
        _execute();
    }

    public DelegateCommand( Action execute ) {
        _execute = execute;
    }

    #region stuff that doesn't affect functionality
    public bool CanExecute( object parameter ) {
        return true;
    }
    public event EventHandler CanExecuteChanged {
        add { }
        remove { }
    }
    #endregion
}

Then your test body might look something like this:

bool wascalled = false;

var execute = new DelegateCommand(
    () => {
        wascalled = true;
    } );

var window = new Window();
SomeClass.SetClose( window, execute );

// does the window need to be shown for Close() to work? Nope.

window.Close();

AssertIsTrue( wascalled );

This is an over-simplified example. There are of course other tests you'll want to perform, in which case you should create or find a fuller implementation of DelegateCommand that also properly implements CanExecute, among other things.

Joel B Fant
cinch goes a step further with the RelayCommand by baking your wascalled test into a CommandSucceeded bool property. Your post is helpful in enforcing that SetClose is still a property setter at the end of the day, even if it doesn't look like the simpler normal C# property setters! That is one of the things I wasn't seeing and isn't intuitive for me yet about DP/attached behavior. Cheers
Berryl
Yep. When compiled, those static Get/Set methods are called. Same thing with DPs: it skips the property wrapper and directly calls `SetValue`/`GetValue` on the `DependencyObject`. Good to hear of that in Cinch. It isn't one I've perused yet.
Joel B Fant