Unit test WPF Bindings

后端 未结 5 569
萌比男神i
萌比男神i 2021-01-04 11:08

I am trying to unit test my WPF databindings using the test suit provided by Microsoft Team System. I would like to be able to test the bindings without showing the window

相关标签:
5条回答
  • 2021-01-04 11:21

    you can try Guia. With it you can unit-test your UserControl and check if the data binding is correct. You have to show the window though.

    Here is an example. It starts a new instance of your UserControl and sets its DataContext and then checks if the textbox is set to the right value.

        [TestMethod]
        public void SimpleTest()
        {
            var viewModel = new SimpleControlViewModel() {TextBoxText = "Some Text"};
    
            customControl = CustomControl.Start<SimpleUserControl>((control) => control.DataContext = viewModel);
    
            Assert.AreEqual("Some Text", customControl.Get<TextBox>("textbox1").Value);
    
            customControl.Stop();
        }
    
    0 讨论(0)
  • 2021-01-04 11:29

    Combining advice I came across in a number of SO posts I wrote the following class which works very well to test WPF bindings.

    public static class WpfBindingTester
    {
        /// <summary>load a view in a hidden window and monitor it for binding errors</summary>
        /// <param name="view">a data-bound view to load and monitor for binding errors</param>
        public static void AssertBindings(object view)
        {
            using (InternalTraceListener listener = new InternalTraceListener())
            {
                ManualResetEventSlim mre = new ManualResetEventSlim(false);
    
                Window window = new Window
                {
                    Width = 0,
                    Height = 0,
                    WindowStyle = WindowStyle.None,
                    ShowInTaskbar = false,
                    ShowActivated = false,
                    Content = view
                };
    
                window.Loaded += (_, __) => mre.Set();
                window.Show();
    
                mre.Wait();
    
                window.Close();
    
                Assert.That(listener.ErrorMessages, Is.Empty, listener.ErrorMessages);
            }
        }
    
        /// <summary>Is the test running in an interactive session. Use with Assume.That(WpfBindingTester.IsAvailable) to make sure tests only run where they're able to</summary>
        public static bool IsAvailable { get { return Environment.UserInteractive && Process.GetCurrentProcess().SessionId != 0; } }
    
    
        private class InternalTraceListener : TraceListener
        {
            private readonly StringBuilder _errors = new StringBuilder();
            private readonly SourceLevels _originalLevel;
            public string ErrorMessages { get { return _errors.ToString(); } }
    
            static InternalTraceListener() { PresentationTraceSources.Refresh(); }
    
            public InternalTraceListener()
            {
                _originalLevel = PresentationTraceSources.DataBindingSource.Switch.Level;
                PresentationTraceSources.DataBindingSource.Switch.Level = SourceLevels.Error;
                PresentationTraceSources.DataBindingSource.Listeners.Add(this);
            }
    
            public override void Write(string message) {}
    
            public override void WriteLine(string message) { _errors.AppendLine(message); }
    
            protected override void Dispose(bool disposing)
            {
                PresentationTraceSources.DataBindingSource.Listeners.Remove(this);
                PresentationTraceSources.DataBindingSource.Switch.Level = _originalLevel;
                base.Dispose(disposing);
            }
        }
    }
    
    0 讨论(0)
  • 2021-01-04 11:33

    While looking for a solution to convert WPF binding errors into exception, I figured out that it can also be used in a unit test project.

    The technique is very simple:

    1. Derive a TraceListener that throws instead of logging
    2. Add that listener to PresentationTraceSources.DataBindingSource

    Please see the complete solution on GitHub, it includes a unit test project.

    Failed test in Visual Studio

    0 讨论(0)
  • 2021-01-04 11:41

    Shane, if what you're really worried about is a binding breaking silently, you should look at redirecting the binding traces to somewhere you can examine. I'd start here:

    http://blogs.msdn.com/mikehillberg/archive/2006/09/14/WpfTraceSources.aspx

    Other than that, I agree with Gishu that bindings aren't good candidates for unit testing, mainly due to the automagic going on that Gishu mentioned in the "Epilogue". Instead focus on making sure the underlying class behaves correctly.

    Note, too, that you can get even more robust traces using the PresentationTraceSources class:

    http://msdn.microsoft.com/en-us/library/system.diagnostics.presentationtracesources.aspx

    Hope that helps!

    0 讨论(0)
  • Eyeball it.
    This kind of declarative markup rarely breaks.. unless someone goes in manual and screws it up. Even then, you can fix it within minutes. IMHO the cost of writing such tests far outweigh the benefits.

    Update[Dec3,08]: Alrighty then.
    The test is just testing that the textbox has the value "FirstName" as the Path property of the binding. If I change/refactor FirstName to JustName in the actual data source object, the test would still pass since it is testing against an anonymous type. (Green test when code broken - TDD Antipattern: The Liar) If your aim is to verify that FirstName has been specified in XAML,

    Assert.AreEqual("FirstName", txtBoxToProbe.GetBindingExpression(TextBox.TextProperty).ParentBinding.Path.Path);
    

    If you really must catch broken bindings via unit tests (and don't want to show the UI), use the real data source... struggled for a while and came up with this.

    [Test]
    public void TestTextBoxBinding()
    {
       MyWindow w = new MyWindow();
       TextBox txtBoxToProbe = w.TextBox1;
       Object obDataSource = w;               // use 'real' data source 
    
       BindingExpression bindingExpr = BindingOperations.GetBindingExpression(txtBoxToProbe, TextBox.TextProperty);
       Binding newBind = new Binding(bindingExpr.ParentBinding.Path.Path);
       newBind.Source = obDataSource;
       txtBoxToProbe.SetBinding(TextBox.TextProperty, newBind);
    
       Assert.AreEqual("Go ahead. Change my value.", txtBoxToProbe.Text);
    } 
    

    Epilogue: There's some real covert stuff happening in the call to Window.Show(). It somehow magically sets up the DataItem property after which data binding starts working.

    // before show
    bindingExpr.DataItem => null
    bindingExpr.Status => BindingStatus.Unattached
    
    // after show
    bindingExpr.DataItem => {Actual Data Source}
    bindingExpr.Status => BindingStatus.Active
    

    Once the Binding is Active, I guess you can force textbox updates via code like this..

    txtBoxToProbe.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
    

    Once again, I voice my reluctance against this approach. Getting NUnit to run in STA was a pain..

    0 讨论(0)
提交回复
热议问题