【问题标题】:Why is my unit test of my Silverlight XAML bindings is failing?为什么我的 Silverlight XAML 绑定的单元测试失败了?
【发布时间】:2011-09-07 05:28:46
【问题描述】:

我定义了以下组合框:

<ComboBox x:Name="cmbCurrency" 
          ItemsSource="{Binding IsoCurrenciesList}"
          DisplayMemberPath="Description"
          SelectedValuePath="LocaleID"
          SelectedValue="{Binding CurrencyId, Mode=TwoWay">
</ComboBox>

其中IsoCurrenciesListIEnumerable&lt;IsoCurrency&gt; - 我们定义并在视图模型中声明的类型为:

private IEnumerable<IsoCurrency> isoCurrenciesList;
public IEnumerable<IsoCurrency> IsoCurrenciesList
{
    get { return isoCurrenciesList; }
    set
    {
        isoCurrenciesList = value;
        RaisePropertyChangedEvent("IsoCurrenciesList");
    }
}

我的单元测试创​​建了视图和视图模型的实例,并在本地列表中设置了一些虚拟货币数据:

[TestInitialize]
public void TestInit()
{
    _target = new View();

    _viewModel = new ViewModel();

    var ukp = new IsoCurrency { Code = "GBP", Description = "Pound Sterling", LocaleID = 826 };
    var usd = new IsoCurrency { Code = "USD", Description = "US Dollar", LocaleID = 840 };
    var eur = new IsoCurrency { Code = "EUR", Description = "Euro", LocaleID = 978 };
    _currencies = new List<IsoCurrency> { ukp, usd, eur };

    GetUIElement<Grid>("LayoutRoot").DataContext = _viewModel;
}

private T GetUIElement<T>(string name) where T : UIElement
{
    return (T)_target.FindName(name);
}

然后调用测试方法。这应该设置货币ComboBox.Items(通过ItemsSource 属性)

[Asynchronous]
[TestMethod]
public void TestCurrencySelection()
{
    _target.Loaded += (s, e) =>
        {
            // Set the currency list explicitly
            _viewModel.IsoCurrenciesList = _currencies;

            var currencyCombo = GetUIElement<ComboBox>("cmbCurrency");
            // This assert fails as Items.Count == 0
            CollectionAssert.AreEquivalent(currencyCombo.Items, _currencies, "Failed to data-bind currencies.");

            EnqueueTestComplete();
        };

    TestPanel.Children.Add(_target);
}

我已遵循 Jeremy Likeness's blog 上的指南,但无法通过绑定测试。

我已经尝试测试其他属性的绑定 - 简单的字符串、布尔值和整数,但在任一端所做的更改不会反映到另一端。

我唯一能想到的是,在将视图添加到 TestPanel 以“激活”绑定之后,我还需要执行另一个步骤,但我不知道它可能是什么。

更新

我应该指出,代码在实际应用中运行良好。根据 cmets(尤其是来自 Adam Sills 的 cmets),问题似乎出在我没有发布的代码中 - 即这是我们构建 XAML 的方式或存在差异我们设置DataContext 的方式。至少我可以将精力集中在(希望)正确的领域。

看来控件在视图中的位置确实很重要。 XAML 页面是这样的:

<Grid x:Name="LayoutRoot">
    <VisualStateManager.VisualStateGroups>
        ...
    </VisualStateManager.VisualStateGroups>

    <toolkit:BusyIndicator x:Name="activityControl" 
                           IsBusy="{Binding IsBusy}" 
                           BusyContent="{Binding BusyContent}" >
        <Grid>
            ... The page definition including sub grids, stack panels
                and the combo box I'm testing along with other controls

            <ops:SaveConfirmation Grid.Row="1" Margin="5"
                                  x:Name="saveConfirmation"
                                  SavedState="{Binding VendorSaved, Mode=TwoWay}" />
        </Grid>
    </toolkit:BusyIndicator/>
</Grid>

BusyIndicator 来自 Silverlight 工具包,SaveConfirmation 是我们编写的控件。

如果我在BusyIndicator 上测试IsBusy 绑定,它会按预期工作。但是,如果我在 SaveConfirmation 上测试 SavedState 绑定失败 - 我在测试中将 VendorSaved 属性设置为 true,但是当我获得控件时,绑定值为 false。

var busyIndicator = GetUIElement<BusyIndicator>("activityControl");
Assert.AreEqual(busyIndicator.IsBusy, _viewModel.IsBusy, "Failed to data-bind busy indicator.");

var saveConfirmation = GetUIElement<SaveConfirmation>("saveConfirmation");
Assert.AreEqual(saveConfirmation.SavedState, _viewModel.VendorSaved, "Failed to data-bind saved state");

所以第一个测试通过了,但是第二个失败了。

我需要做些什么来确保元素的绑定一直沿树向下设置?

【问题讨论】:

  • 我拿走了你的代码,消除了供应商,创建了一个非常简单的视图,只有一个网格“LayoutRoot”和一个 ComboBox,并且测试通过了很好(并且 Items.Count == 3)。
  • @Adam - 是的,Vendor 是在尝试测试其他绑定时留下的。它有效的事实是个好消息——它应该有效。所以看来问题出在我没有发布的内容中。
  • FWIW - XAML:textsnip.com/ec7b6a ViewModel:textsnip.com/cec377 测试:textsnip.com/d32a77
  • 是只有unittest失败了,还是现实世界也失败了?
  • @Bubblewrap - 只有单元测试失败。一切都在现实世界中有效。鉴于 Adam Sills 让它与一个简单的视图和视图模型一起工作,我没有发布的代码中肯定有一些东西导致了问题。至少我现在知道去哪里看;)

标签: c# silverlight unit-testing xaml mvvm


【解决方案1】:

感谢Joel CAdam Sills 我已经完成了这项工作。

线索是,在示例中,正在测试的控件是 LayoutRoot 的直接子代,实际上,当我在我的页面上测试这些控件时,这些测试也通过了。

所以我有两个解决方案:

1) 更改测试触发的事件:

[TestMethod]
[Description("Tests that the currency ComboBox is databound correctly")]
public void TestCurrencySelection()
{
    _target.LayoutUpdated += (s, e) =>
        {
            SetupViewModel();

            var currencyCombo = GetUIElement<ComboBox>("cmbCurrency");
            CollectionAssert.AreEquivalent(currencyCombo.Items,
                                           _currencies,
                                           "Failed to data-bind currencies list.");

            Assert.AreEqual(currencyCombo.SelectedValue,
                            _viewModel.CurrencyId,
                            "Failed to data-bind selected currency.");
        };

    TestPanel.Children.Add(_target);
}

直到视图为Loaded 之后绑定才会初始化,但它们在LayoutUpdated 触发时才会初始化。通过进行此更改,我现在可以在可视化树的任何级别上可靠地测试绑定。

如果我在一次测试中测试页面上的所有绑定,那就没问题了 - 这不是一个很好的做法。

2) 使用我正在测试的控件的父 UIElement 而不是 LayoutRoot 并仍然处理 Loaded 事件。这意味着我必须为每个容器元素添加名称,但这确实意味着我可以更合理地拆分测试。

【讨论】:

    【解决方案2】:

    我的猜测是因为您在 Loaded 处理程序中执行 _viewModel.IsoCurrenciesList = _currencies;,而不是像博客中的示例那样填充现有的 ObservableCollection 属性。在 Dispatcher 上的当前调用完成之前,绑定可能不会更新。虽然我对 Silverlight 不够熟悉,但不能肯定地说。

    要对此进行测试,您可以在将 viewModel 设置为控件上的 DataContext 之前尝试设置 _viewModel.IsoCurrenciesList

    【讨论】:

    • +1 - IsoCurrenciesList 的绑定在当前方法完成之前不会评估和填充项目列表。为什么不直接在 TestInit 中设置 IsoCurrenciesList?您是在测试您的绑定还是在测试您的 INotifyPropertyChanged 实现?您不必同时测试两者。
    • @Adam - 我们正在测试绑定,INotifyPropertyChanged 在其他地方进行了测试。虽然我最初在设置DataContext 之前设置了IsoCurrenciesList,但这也不起作用。
    【解决方案3】:

    您已为 ComboBox 包含 XAML,但未包含页面的其余部分。在您的测试中,您有GetUIElement&lt;Grid&gt;("LayoutRoot").DataContext = _viewModel;。在您遵循的示例中,他定义:

    <Grid x:Uid="LayoutRoot" x:Name="LayoutRoot" Background="White"
        DataContext="{Binding Source={StaticResource VMLocator},Path=Cascadia}">
    

    然后他正在测试绑定的控件直接嵌套在该网格中。您的页面设置方式是否相同?

    【讨论】:

    • 我认为这是我们在视图的构造函数中执行的普通绑定。我明天上班时必须仔细检查 - 但它可能是有希望的询问线。
    • 组合框不直接在页面上,但GetUIElement 找到正确的控件,因为DisplayMemberPathSelectedValuePath 属性具有预期值。
    • 嗯 - 控件的位置似乎确实很重要。如果我测试直接在LayoutRoot 下的控件的绑定,它可以工作。如果失败了,只要我把其他东西放在路上。
    • 尝试将GetUIElement&lt;Grid&gt;("LayoutRoot") 更改为ComboBox 的父控件。
    • @Joel - 好主意,但问题不在于我没有找到要测试的控件,而是在Loaded 事件触发时尚未设置绑定.我已经取得了进展,当我整理出最后剩下的问题时,我可能会发布一个带有结果的答案。
    猜你喜欢
    • 1970-01-01
    • 2014-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-05
    • 1970-01-01
    • 1970-01-01
    • 2012-09-16
    相关资源
    最近更新 更多