为什么我对Silverlight XAML绑定的单元测试失败了?

时间:2011-06-02 16:37:10

标签: c# silverlight unit-testing xaml mvvm

我已经定义了以下组合框:

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

其中IsoCurrenciesListIEnumerable<IsoCurrency> - 我们定义的类型,并在视图模型中声明为:

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以“激活”绑定后,我需要做另一个步骤,但我不知道它可能是什么。< / p>

更新

我应该指出代码在实际应用程序中运行良好。基于评论(特别是来自Adam Sills的评论),看起来问题在于我没有发布的代码 - 也就是说,这是我们构建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 Toolkit中的一个,SaveConfirmation是我们编写的控件。

如果我测试IsBusy上的BusyIndicator绑定按预期工作。但是,如果我测试失败的SavedState上的SaveConfirmation绑定 - 我在测试中将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");

所以第一次测试通过,但第二次测试失败。

我需要做些什么才能确保设置树中所有元素的绑定?

3 个答案:

答案 0 :(得分:2)

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

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

答案 1 :(得分:1)

您已为您的ComboBox添加了XAML,但未包含页面的其余部分。在您的测试中,您有GetUIElement<Grid>("LayoutRoot").DataContext = _viewModel;。在您关注的示例中,他定义:

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

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

答案 2 :(得分: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事件。这意味着我必须为每个容器元素添加名称,但这确实意味着我可以更逻辑地拆分测试。