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.
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.
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.
Have fun!
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
WPF does support MEF, but with a different API. First create a CompositionContainer, supplying one or more catalogs, then call SatisfyImportsOnce. For more info on MEF, check out my webcast: https://blog.tonysneed.com/2010/11/19/webinar-mef-explained.