Leveraging expression trees to unit test ViewModel classes

Introduction: In this article, I’m describing a technique which leverage the expression trees of C# 3.0 in order to facilitate the unit testing of ViewModel’s properties. My final goal is to be able to unit test a ViewModel property in 1 line.

Without any doubt MVVM is now the most used framework to leverage WPF and Silverlight functionalities in the best way ! During the last Mix, 3 sessions were dedicated to this methodology (you can watch the videos online here).

As you already know one of the key advantage of the MVVM methodology is to improve the testability of the overall application by reducing the amount of code in the code-behind and producing ViewModel classes which are testable. We use to say that ViewModel classes are testable because:

  • they are not coupled to UI concepts (controls, focus, keyboard input…)
  • they can wrap model objects using interfaces (for instance a PersonViewModel wraps a IPerson object)
  • they are not subclassing a UI control (such as Button or Window)

Today I’d like to share a technique I’m using to facilitate the unit tests of some properties of my ViewModel classes.

Let’s use a very simple ViewModel class as example:

public class PersonViewModel : ViewModelBase
{
  private IPerson person;
  private bool isSelected;

  public string Name 
  {
     get
     {
        return this.person.Name;
     }
     set
     {
        this.person.Name = value;
        this.OnPropertyChanged("Name");
     }
  }

  public bool IsSelected
  {
    get
    {
      return this.isSelected;
    }
    set
    {
      this.isSelected = value;
      this.OnPropertyChanged("IsSelected");
    }
  }

  // rest of the code omitted for simplicity
}

The Name property, as usually with the MVVM pattern gets its value from the wrapped model object. The easiest way to unit test this property is to use a mocking library. Here is a example using MOQ (my favourite mocking library):

[Test]
public void TestName()
{
  var mockPerson = new Mock();
  
  var vm = new PersonViewModel(mockPerson.Object);

  vm.Name = "Jeremy";

  // verify that the Name property of the IPerson interface has been set
  mockPerson.VerifySet(p => p.Name = "Jeremy");
}

The Selected property is different because it doesn’t wrap a model property. It’s an information that is added to the ViewModel layer in order to control a UI-related property (for example the IsSelected property of a ListBoxItem). This technique is heavily used to have ViewModel classes interact with the WPF or Silverlight TreeView or ListBox control (you can check out this excellent article of Josh Smith for more detail).

In order to unit test this property, we must:
1/ ensure the PropertyChanged event of the INotifyPropertyChanged is raised properly
2/ ensure we can write a value and read back the correct value

Here is a sample code which does this unit test:

[Test]
public void TestName()
{
var vm = new  PersonViewModel();
bool propertyChanged = false;

vm.PropertyChanged += (s, e) => propertyChanged = e.PropertyName ==  "Name";
vm.Name = "newName";

Assert.IsTrue(propertyChanged);
Assert.AreEqual("newName", vm.Name);
}

It quickly become cumbersome to copy/paste this unit test for all the ViewModel properties we have. That’s the reason I started thinking about another way to do it…

Here is the feature I’m proposing:

[Test]
public void TestName()
{
var vm = new PersonViewModel();
TestHelper.TestProperty(vm, v => v.IsSelected);
}

In this sample, I’m telling I want to test the IsSelected property of the PersonViewModel type. The advantages are:
1/ less code involved : 1 line to test 1 property
2/ intellisense support in order to prevent typing error and no more “magic” string to give the name of the property
3/ refactoring the name of the property will refactor this sample code too
4/ automatic generation of default test values behind the scene

How does it works ?

  • TestProperty treats the second parameter as an Expression<Func> and not as a Func directly
  • Using expression tree (the “v => v.IsSelected” part),  I’m able to retrieve the name of the property and its type
  • Using reflection, I’m able to get and set the value
  • Depending on the type of the property (string, bool, int, double), I have default values write and read back (with a test to ensure that the PropertyChanged event has been raised properly).

Here is the code of the TestPropertyMethod:

public static void TestProperty(T viewmodel, Expression> expression)
    where T : INotifyPropertyChanged
{
    if(expression.Body is MemberExpression)
    {
        MemberExpression memberExpression = (MemberExpression) expression.Body;

        if (expression.Body.Type == typeof(bool))
        {
            TestViewModelProperty(viewmodel, memberExpression.Member.Name, true, false);
        }
        else if (expression.Body.Type == typeof(string))
        {
            TestViewModelProperty(viewmodel, memberExpression.Member.Name, "value1", "value2");
        } 
        else if (expression.Body.Type == typeof(int))
        {
            TestViewModelProperty(viewmodel, memberExpression.Member.Name, 1, 99);
        }
        else if (expression.Body.Type == typeof(double))
        {
            TestViewModelProperty(viewmodel, memberExpression.Member.Name, 1.0, 99.0);
        }
        else
        {
            throw new NotSupportedException("Type is not supported");
        }
   }
}

And the TestViewModelProperty:

private static void TestViewModelProperty(T viewModel, string propertyName, U value1, U value2)
    where T : INotifyPropertyChanged
{
    bool propertyChanged;
    viewModel.PropertyChanged += (s, e) => propertyChanged = e.PropertyName == propertyName;

    propertyChanged = false;
    viewModel.SetValue(propertyName, value1);
    Assert.IsTrue(propertyChanged);
    Assert.IsTrue(viewModel.GetValue(propertyName).Equals(value1));

    propertyChanged = false;
    viewModel.SetValue(propertyName, value2);
    Assert.IsTrue(propertyChanged);
    Assert.IsTrue(viewModel.GetValue(propertyName).Equals(value2));
}

I’m using 2 extensions methods in order to get and set value from the ViewModel object using reflection. Here they are:

private static T GetValue(this object obj, string propertyName)
{
    var propertyInfo = obj.GetType().GetProperty(propertyName);
    return (T)propertyInfo.GetValue(obj, null);
}

private static void SetValue(this object obj, string propertyName, T value)
{
    var propertyInfo = obj.GetType().GetProperty(propertyName);
    propertyInfo.SetValue(obj, value, null);
}

Please feel free to download the source code of the ViewModelTestHelper class.

10 thoughts on “Leveraging expression trees to unit test ViewModel classes

  1. Cool stuff, thanks !

    Just an idea : for more convenience, why not declare the TestHelper.TestProperty method as an extension method ? This would make the unit test code even simpler :

    vm.TestProperty(v => v.IsSelected);

  2. Thank you for the feedback Thomas 🙂

    Yes I could have done an extension method. Is it possible to do that on an interface (INotifyPropertyChanged) and not on a type ?

  3. Very cool Jérémy !
    For sure, I’ll try this code in my next test project.
    I think there’s a small error in your third source code sample:

    vm.PropertyChanged += (s, e) => propertyChanged = e.PropertyName == “newName”

    must be changed to

    vm.PropertyChanged += (s, e) => propertyChanged = e.PropertyName == “Name”;

    Good work !

    Fred

  4. var vm = new SupportPackageDetailsViewModel();
    TestHelper.TestViewModelHelper.TestProperty(vm, v => v.Code);
    When I call the helper function got compile error:

    Error 1
    The type ‘PC5.SupportPackage.ViewModels.SupportPackageDetailsViewModel’
    cannot be used as type parameter ‘T’ in the generic type or method ‘PC5.SupportPackageSiteTests.TestHelper.TestViewModelHelper.TestProperty(T, System.Linq.Expressions.Expression<System.Func>)’.
    There is no implicit reference conversion from ‘PC5.SupportPackage.ViewModels.SupportPackageDetailsViewModel’ to ‘System.ComponentModel.INotifyPropertyChanged’.

  5. Emily,

    Based on the error raised by the compiler I think the SupportPackageDetailsViewModel does not implement the INotifyProperyChanged interface…

    Let me know if it works once you implement that interfac 🙂

  6. Hi,Jeremy: Thanks for quick reply, you are right, after my class implement the INotifyProperyChanged then the test passed.
    I have another problem is after I run my test, in TestViewModelProperty() function, the ‘propertyChanged’ was not set to true, so line ‘Assert.IsTrue(propertyChanged);’ got failed.
    Do you think the raise event function in my class did raise the event? the method in my class is as:
    protected void OnPropertyChanged(string name)
    {
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
    {
    handler(this, new PropertyChangedEventArgs(name));
    }
    }

    Thanks

  7. Actually you have to call OnPropertyChanged manually each time. For example if you have a Name property, the code should loos like the following:

    private string name;
    public string Name
    {
    get { return this.name }
    set
    {
    this.name = value;
    this.OnPropertyChanged(“Name);
    }
    }

Leave a Reply

Your email address will not be published. Required fields are marked *