Simple MVVM Toolkit: Unit Testing with Dependency Injection and MEF

One of the main benefits of using the MVVM (Model, View, ViewModel) design pattern is to support better testability.  Having to click through screens to test an application can be tedious and time-consuming, and it may not provide good code coverage or regression testing.  By abstracting the View into a separate ViewModel, you can independently test the ViewModel to make sure it behaves as expected.  The unit testing framework is simple another consumer of the View-Model.  In fact, thinking about the testability of ViewModels can help remind you to keep visual elements, such as brushes and dialogs, out of the ViewModel.

NOTE: You can obtain the code for this blog post from the SimpleMvvm-Injection sample project that’s part of the download for my Simple Mvvm Toolkit. Support for DI in the toolkit is explained in the online documentation.

Whether or not you subscribe to the tenants of Test-Driven Development (TDD), you should test your ViewModels without any dependency on WCF services or databases.  This keeps the tests simple, focused and fast.  That doesn’t mean you will not want to also perform “integration tests” by connecting to live services that retrieve and persist real data.

What this means is that you need a mechanism for switching back and forth between real and mock data sources depending on whether you are performing unit or integration tests.  Enter Dependency Injection!  DI (sometimes referred to as IoC, or Inversion of Control) provides a way to create different sets of objects depending on what you are doing.  There are a number of DI containers out there, including Unity (MS Patterns) and Ninject, and Windsor.

The problem I find with adopting one of these is that, while each offers a number of bells and whistles, they may be overkill when all you want to do is enable unit testing for your MVVM app.  This is where Managed Extensibility Framework (MEF) comes in.  The nice thing about MEF is that it comes with .NET 4.0 and Silverlight right out of the box and is focused on the simple task of providing instances based on an agreed-upon contract.  (For a complete explanation of MEF see my blog post and screencast.)

The good news is that you don’t have to completely grok MEF in order to take advantage of it.  That’s because I’ve incorporated MEF into my Simple MVVM Toolkit to support DI for unit testing.  When creating an app using the toolkit, add an “Injected” ViewModel Locator using the supplied Visual Studio item template.

injected-locator

What you get is a class that maintains a dictionary of ViewModels, each constructed by supplying a service agent, whose job it is to supply data to the ViewModel.  To support testing, you’ll want two service agents, one using mock data and the other using real data, each of which implements a common interface.  For example, an ICustomerServiceAgent might have a method called GetCustomer, with a customerId parameter, as well as a completion callback that returns either a Customer or an Exception.

public interface ICustomerServiceAgent
{
    void GetCustomer(int customerId, 
        Action<Customer, Exception> completed);
}

Both RealCustomerServiceAgent and MockCustomerServiceAgent classes implement this interface, with the real service agent invoking a WCF service operation to obtain the customer from a database, and the mock service agent providing a fabricated customer.  Notice that the interface supports asynchronous operations.  This means that the mock service agent would use a BackgroundWorker component to fake an async service call.

public class MockCustomerServiceAgent : ICustomerServiceAgent
{
    public void GetCustomer(int customerId, 
        Action<Customer, Exception> completed)
    {
        // Use background worker to simulate async operation
        var bw = new BackgroundWorker();

        // Handle worker started
        bw.DoWork += (s, ea) =>
        {
            // Create a new customer
            Thread.Sleep(TimeSpan.FromSeconds(2));
            ea.Result = new Customer
            {
                Id = (int)ea.Argument,
                Name = "John Doe",
                CustomerType = CustomerType.Mock
            };
        };

        // Hander worker completed
        bw.RunWorkerCompleted += (s, ea) =>
        {
            if (ea.Error != null)
            {
                completed(null, ea.Error);
            }
            else
            {
                completed((Customer)ea.Result, null);
            }
        };

        // Start async operation
        bw.RunWorkerAsync(customerId);
    }
}

The Injected ViewModel Locator uses MEF to supply either a real or mock service agent, depending on an attribute you attach to each service agent.  The ServiceAgentExport attribute is defined in the toolkit library and has two parameters: the type of the service agent interface, and an AgentType enum, which has values for Unspecified, Real and Mock.  Here’s what the MockCustomerServiceAgent looks like with the MEF export attribute and an AgentType.Mock parameter.

[ServiceAgentExport(typeof(ICustomerServiceAgent), 
    AgentType = AgentType.Mock)]
public class MockCustomerServiceAgent : ICustomerServiceAgent
{

This lets the ViewModel locator know that this is a mock service agent.  The real service agent has the same attribute, but with an AgentType.Real attribute parameter.

[ServiceAgentExport(typeof(ICustomerServiceAgent), 
    AgentType = AgentType.Real)]
public class RealCustomerServiceAgent : ICustomerServiceAgent
{

After adding an injected ViewModel locator to the project, you will see a number of TODO items.  Basically, you need to specify the service agent interface and flesh out a ServiceAgents property which specifies kind service agent you want to use.

public class InjectedViewModelLocator
{
    // View model dictionary
    private Dictionary<string, ViewModelBase> viewModels =
        new Dictionary<string, ViewModelBase>();

    // Add a member for ICustomerServiceAgent
    private ICustomerServiceAgent serviceAgent;

    // Specify default service agent type
    private AgentType agentType = AgentType.Real;

    [ImportMany]
    public Lazy<ICustomerServiceAgent, IServiceAgentMetadata>[] 
        ServiceAgents
    {
        set
        {
            var lazy = (from sa in value
                        where sa.Metadata.AgentType == agentType
                        select sa).FirstOrDefault();
            if (lazy != null) serviceAgent = lazy.Value;
        }
    }

If you’re not familiar with MEF, this may appear somewhat confusing, because the ServiceAgents property only has a setter and is an array of Lazy<T, TMetadata>.  What we’re doing here is simply asking MEF to give us one or more ICustomerServiceAgents.  What the Lazy type does is defer creation of the class implementing ICustomerServiceAgent until we access the Value property, which we only do if the AgentType matches what we’ve set for the agentType field.  In other words, we will set serviceAgent to either RealCustomerServiceAgent or MockCustomerServiceAgent, depending on whether we set agentType to Real or Mock.  This is not done until you call CompositionInitializer.SatisfyImports, which takes place in the locator’s ctor.

public InjectedViewModelLocator()
{
    // Use MEF to inject service agents
    CompositionHost.Initialize(new DeploymentCatalog());
    CompositionInitializer.SatisfyImports(this);

    // Throw exception if service agent not initialized
    if (serviceAgent == null)
    {
        throw new Exception("Service agent not initialized");
    }

    // Call AddCustomerViewModel methods passing serviceAgent
    AddCustomerViewModel(serviceAgent);
}

The reason why we’re going to all this trouble is to be able to test the ViewModel.  For that we’ll leverage the Silverlight Unit Testing Framework, which ships with the Silverlight Toolkit.  We add a new project to the solution by selecting the Silverlight Unit Test Application project template.

sl-test

What we get is a project containing a test class.  Because our service calls are asynchronous, we need to derive the class from SiverlightTest, add an Asynchronous attribute to the test method, and call the EnqueueTestComplete method in the completion callback.

[TestClass]
public class CustomerViewModelTest : SilverlightTest
{
    [TestMethod, Asynchronous]
    public void CreateCustomerTest()
    {
        // Create a view-model locator
        InjectedViewModelLocator locator = 
            new InjectedViewModelLocator();
        locator.AgentType = AgentType.Mock;

        // Get the customer view model
        var vm = locator["CustomerViewModel"] as CustomerViewModel;
        Assert.IsNotNull(vm);

        // Handle error
        vm.ErrorNotice += (s, ea) =>
        {
            // Fail the test showing the error message
            Assert.Fail(ea.Data.Message);

            // Signal we're done
            EnqueueTestComplete();
        };

        // Handle property changed
        vm.PropertyChanged += (s, ea) =>
        {
            if (ea.PropertyName == "Model")
            {
                // Take a look at the customer
                Customer customer = vm.Model;
                Assert.IsNotNull(customer);
                Assert.Equals(customer.Name, "John Doe");

                // Signal we're done
                EnqueueTestComplete();
            }
        };

        // Use vm to create a customer
        vm.CreateCustomer();
    }
}

To perform the test you have to actually run the test project – unlike traditional uniting testing in Visual Studio.  When you do so, you’ll see the test results displayed in the browser.

test-result

Have fun!

About Tony Sneed

Sr. Software Solutions Architect, Hilti Global Application Software
This entry was posted in Technical and tagged , , . Bookmark the permalink.

2 Responses to Simple MVVM Toolkit: Unit Testing with Dependency Injection and MEF

  1. fstipps says:

    Hi Tony,

    is it possible that this example only works for Silverlight projects?
    I tried to work it through in a WPF SimpleMVVMToolkit application, but WPF seems to lack CompositionHost and CompositionInitializer ..

    Rgds,
    Fredrik

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.