简体   繁体   中英

Why is my unit test of my Silverlight XAML bindings is failing?

I've got the following combobox defined:

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

Where IsoCurrenciesList is an IEnumerable<IsoCurrency> - the type being defined by us and declared in the view model as:

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

My unit test creates an instance of the view and view model and sets up some dummy currency data in a local list:

[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);
}

Then the test method is called. This should set the currency ComboBox.Items (via the ItemsSource property)

[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);
}

I've followed the guidelines on Jeremy Likeness's blog but I can't get the bindings test to pass.

I've tried testing the bindings of other properties - simple strings, booleans and integers but the changes made at either end aren't reflected to the other.

The only thing I can think of is that there is another step I need to do after adding the view to the TestPanel to "activate" the bindings, but I've no idea what it could be.

UPDATE

I should point out that the code works fine in the actual application. Based on the comments (particularly those from Adam Sills) it looks like the problem lies in the code I haven't posted - ie it's something in the way we've structured the XAML or there's a difference in the way we set the DataContext . At least I can concentrate my efforts in (hopefully) the right area.

It appears that the position of the control in the view does matter. The page XAML is something like this:

<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>

The BusyIndicator is the one from the Silverlight Toolkit and SaveConfirmation is a control we've written.

If I test the IsBusy binding on the BusyIndicator that works as expected. However, if I test the SavedState binding on the SaveConfirmation that fails - I set the VendorSaved property to true in the test but when I get the control the bound value is 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");

So the first test passes, but the second fails.

What do I need to do to ensure that the bindings of elements all the way down the tree are set up?

My guess is it's because you're doing _viewModel.IsoCurrenciesList = _currencies; in the Loaded handler instead of filling an existing ObservableCollection property like the example from the blog does. It is possible that the binding will not be updated till after the current call on the Dispatcher completes. Though i'm not familiar enough with Silverlight to say this for sure.

To test this, you could try setting _viewModel.IsoCurrenciesList before you set the viewModel as the DataContext on your control.

You've included the XAML for your ComboBox, but not the rest of your page. In your test you have GetUIElement<Grid>("LayoutRoot").DataContext = _viewModel; . In the example you're following, he defines:

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

and then the controls on which he's testing the bindings are nested directly inside that grid. Is your page setup the same way?

Thanks to Joel C and Adam Sills I've got this working.

The clue was that in the example the controls that were being tested were direct children of the LayoutRoot and indeed when I tested these controls on my page these tests passed too.

So I have two solutions:

1) Change the event on which the tests fire:

[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);
}

The bindings aren't initialised until after the view is Loaded but they are by the time LayoutUpdated fires. By making this change I can now reliably test the bindings at any level of the visual tree.

This would be OK if I were testing all the bindings on the page in one test - which isn't really good practice.

2) Use the parent UIElement of the control I'm testing instead of LayoutRoot and still handle the Loaded event. This means that I have to add names to each of these container elements, but it does mean I can split the tests up more logically.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM