Welcome to Vista Squad Sign in | Join | Help
Unit Testing and INotifyPropertyChanged

I’ve been busy with a Silverlight project where our models are all implementing INotifyPropertyChanged.  I’m not a big fan of INotifyPropertyChanged because of the need to raise the event with the property name as a string.  This can lead to numerous spelling mistakes and broken code.

So the easiest solution to this is to unit test.  But the unit tests are all the same and very monotonous to write. Attach an event handler, set property Assert that the property was changed properly.

Lets change this today to something more automatic.  I’ve written a class that allows you to pass an object in that implements INotifyPropertyChanged and it will test all the properties for you.

   1:  public class PropertyTester<T> 
   2:          where T : INotifyPropertyChanged
   3:      {
   4:          List<string> _propertyChangedProperties = new List<string>();
   5:   
   6:          public void AssertCorrectProperties
   7:              (T item, Action<T> propertySetter, string message)
   8:          {
   9:              //Subscribe to the event
  10:              item.PropertyChanged += 
  11:                  new PropertyChangedEventHandler(Type_PropertyChanged);
  12:              //Call the action to set the items
  13:              propertySetter(item);
  14:   
  15:              //Get all the property names that have public set methods 
  16:              //that we should compare
  17:              //this obviously makes the assumption that all 
  18:              //properties accessible sets call NotifyPropertyChanged
  19:              var propertyNames =
  20:                  from property in
  21:                      item.GetType().GetProperties(
  22:                          BindingFlags.Instance | BindingFlags.Public)            
  23:                  where property.CanWrite 
  24:                  select property.Name;
  25:   
  26:              CollectionAssert.AreEquivalent
  27:                  (propertyNames.ToList(), 
  28:                  _propertyChangedProperties, 
  29:                  message);
  30:          }
  31:   
  32:          private void Type_PropertyChanged
  33:              (object sender, PropertyChangedEventArgs e)
  34:          {
  35:              //Not the best, but without a HashSet<T> in Silverlight, 
  36:              //I'm happier with a list and
  37:              //a Contains over a Dictionary<T,K> for testing purposes
  38:              if (!_propertyChangedProperties.Contains(e.PropertyName))
  39:              {
  40:                  _propertyChangedProperties.Add(e.PropertyName);
  41:              }
  42:          }
  43:      }

The first thing you notice is that to create the PropertyTester, your generic type must implement INotifyPropertyChanged.  There is an assumption that this class makes and this is that all your publicly settable properties actually do call NotifyPropertyChanged().

Lets run through this then.  In the AssertCorrectProperties method, you pass in an instance to test and an Action<T> that we will call to set the properties and fire NotifyPropertyChanged.  The method subscribes to the NotifyPropertyChanged event and calls the action.  In the event handler all we are doing is creating a list and adding each of the property names to the list.  The method then enumerates over all the properties of T to find the publicly settable properties and compares this list to make sure it is equivalent to the property names that were fired.

As long as the properties are equivalent then we have a successful pass of the test.  I’ve attached some code below which shows what a potential test could look like.

   1:  public class Pet : INotifyPropertyChanged
   2:      {
   3:          public event PropertyChangedEventHandler PropertyChanged;
   4:   
   5:          private string _name;
   6:          /// <summary>
   7:          /// Gets or sets the back text
   8:          /// </summary>
   9:          public string Name
  10:          {
  11:              get { return _name; }
  12:              internal set
  13:              {
  14:                  if (_name != value)
  15:                  {
  16:                      _name = value;
  17:                      NotifyPropertyChanged("Name");
  18:                  }
  19:              }
  20:          }
  21:   
  22:          private DateTime _dateOfBirth;
  23:          public DateTime DateOfBirth
  24:          {
  25:              get { return _dateOfBirth; }
  26:              internal set
  27:              {
  28:                  if (_dateOfBirth != value)
  29:                  {
  30:                      _dateOfBirth = value;
  31:                      NotifyPropertyChanged("DateOfBirth");
  32:                  }
  33:              }
  34:          }
  35:   
  36:          protected void NotifyPropertyChanged(string PropertyName)
  37:          {
  38:              OnNotifyPropertyChanged(PropertyName);
  39:          }
  40:   
  41:          protected virtual void OnNotifyPropertyChanged(
  42:              string PropertyName)
  43:          {
  44:              if (this.PropertyChanged != null)
  45:              {
  46:                  this.PropertyChanged(this, 
  47:                      new PropertyChangedEventArgs(PropertyName));
  48:              }
  49:          }
  50:      }
  51:   
  52:      [TestClass]
  53:      public class PetTest
  54:      {
  55:          [TestMethod]
  56:          public void TestProperties()
  57:          {
  58:              Pet dog = new Pet
  59:              {
  60:                  Name = "Fido",
  61:                  DateOfBirth = DateTime.Now
  62:              };
  63:              PropertyTester<Pet> tester = new PropertyTester<Pet>();
  64:              Action<Pet> propertySetter = new Action<Pet>(p =>
  65:              {
  66:                  p.DateOfBirth = DateTime.MaxValue;
  67:                  p.Name = "Fred";
  68:              });
  69:              tester.AssertCorrectProperties(
  70:                  dog, 
  71:                  propertySetter, 
  72:                  "Properties are not set correctly.");
  73:          }
  74:      }
Posted: Thursday, October 09, 2008 4:51 PM by Ray Booysen

Comments

DotNetKicks.com said:

You've been kicked (a good thing) - Trackback from DotNetKicks.com

# December 1, 2008 4:10 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS