Reputation: 137188
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 - i.e. 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?
Upvotes: 3
Views: 726
Reputation: 137188
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.
Upvotes: 1
Reputation: 7526
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.
Upvotes: 2
Reputation: 5567
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?
Upvotes: 1