Oct 20, 2008

Sharing Mock Data for Unit Tests and Blend

I have been diving in to Inversion of Control and specifically the Dependency Injection / Inversion (DI) pattern recently and I have been trying to find legitimate ways to fit the pattern into my workflow. First off I will say I really like the DI pattern and it has plenty benefits which I will try to discuss in a series of blog posts in the future (or if you want to hear about it in person come to my presentation at Code Camp)

What I have been to trying to work out this week is how can I provide a seamless way for developers and designers to share mock data between unit tests and Blend. The prerequisite for this approach to make sense is that you are doing unit tests during your development process. Assuming that is the case then I think the following solution could work out nicely.

If you are following the DI pattern then you will know that it is easy to swap out different concrete types for a given interface. The DI patterns focuses on the idea of programming against interfaces. That way, your code is decoupled from the details of the implementing type. This allows you to easily swap out the real version of your data classes for Mock versions. This gives you a nice way to cleanly unit test your code without being dependent on any sort of external environment (SQL DB, Web Services, etc..)

So if you are following this pattern and creating mock versions of your data classes why not share those with Blend designers? One of the complaints I hear often from Blend designers is that they struggle to create data visualizations when they don't have any data (duh). Using mock data classes is a great way to provide sample data for designers to use in Blend so they can see what the screen will look like when it is connected to data. If you are already creating the mocks for unit test purposes then sharing with Blend isn't any much more work on your part.

If you separate your Interface definitions and your Mock Data classes in to separate .NET projects like this

  

Then you have a way for your Unit Tests and your Main application to leverage the Mock Data. A unit test that uses the the MockPerson type might look something like this

var person = new MockPerson();Assert.IsNotNull(person); 
Assert.AreEqual(person.GetFirstName(), "MockFirstName");
Assert.AreEqual(person.GetLastName(), "MockLastName");
Assert.AreEqual(person.GetFullName(), "MockFirstName MockLastName");


Where the implementation of the MockPerson class looks like this



public class MockPerson : IPerson
{
public string GetFirstName() { return "MockFirstName"; }
public string GetLastName() { return "MockLastName"; }
public string GetFullName() { return string.Format("{0} {1}", GetFirstName(), GetLastName()); }
}


Notice how MockPerson implements the IPerson interface. This is key to allowing the use 
of Mock data classes. I will dive deeper
into programming against interfaces and the DI
pattern in future posts.


Now if we want to use this MockPerson class to provide sample data to a designer in Blend 
we need first understand what Blend does when constructing your XAML file. My first
thought was that Blend would call my constructor in the XAML.cs file for the XAML file
I was opening in Blend.


However I was wrong. Blend will not call your constructor but it will construct any objects 
that are specified in the XAML markup itself. This means that if you set the DataContext
attribute of your XAML file in XAML like this


<Window x:Class="IoCSample.Window1" 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:IoCSample="clr-namespace:IoCSample"
Title="Window1"
Height="300"
Width="300">

<Window.DataContext>
<IoCSample:ViewModel />
</Window.DataContext>


Then you will have a piece of code that you can hook into to check and see if you are



in DesignMode. To check and see if you are in DesignMode you can use the following



code:



DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(DesignerProperties.IsInDesignModeProperty, typeof(System.Windows.DependencyObject));
var isInDesignMode = (bool)descriptor.Metadata.DefaultValue;
if (isInDesignMode)
{
}


Now to actually provide MockData to Blend we want to check this IsInDesignMode property and provide Mock implementations of the data classes we need. Sticking with the example above of IPerson and MockPerson, let's say that our ViewModel class (which is specified as the data context for the XAML file we are opening in Blend) has a property of type IPerson. Defined like this



public class MockPerson : IPerson
{
public string GetFirstName()
{
return "MockFirstName";
}

public string GetLastName()
{
return "MockLastName";
}

public string GetFullName()
{
return string.Format("{0} {1}", GetFirstName(),
GetLastName());
}
}




And say we are binding to that property in our XAML like this (Remember our DataContext is set to the ViewModel class shown above so  {Binding PersonFirstName} refers to the PersonFirstName property on the ViewModel class)



<TextBlock Text="{Binding PersonFirstName}" />  


Notice the check in the ViewModel constructor above to see if we are in design mode. Now we have a place to check if we are in design mode and create a MockPerson object if we are. To do that we will modify the ViewModel constructor to look like this:



public ViewModel() 
{
DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor. FromProperty(DesignerProperties.IsInDesignModeProperty, typeof(System.Windows.DependencyObject));var isInDesignMode = (bool)descriptor.Metadata.DefaultValue;
if (isInDesignMode)
{
Person = new MockPerson();
}
}


Remember from above that the MockPerson class has some "Mock" data (hence the name) in the property getters. So a call to Person.GetFirstName(); (which is called from the Binding in the XAML file {Binding PersonFirstName})  returns "MockFirstName" .



Now when we open this file in Blend it will fire the ViewModel constructor and make the check to see if we are in DesignMode, if we are it will construct a new MockPerson object and set the ViewModel IPerson property to the MockPerson instance. This will provide MockData to the Blend designer. This is a very basic approach to this solution. In a future blog post I will describe a more detailed approach that involves a custom markup extension and ties in to an Inversion of Control container to provide a generic way of registering Mock object dependencies with your IoC container for DesignTime and UnitTest development. But for now this solution should get you started and help you provide a richer design time experience for Blend designers.

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home