MVVM

Writing a Testable Presentation Layer with MVVM

Brent Edwards

Download the Code Sample

With traditional applications from the Windows Forms days, the standard practice for testing was to lay out a view, write code in the view’s codebehind, then run the app as a test. Fortunately, things have evolved a bit since then.

The advent of Windows Presentation Foundation (WPF) brought the concept of data binding to a whole new level. It has allowed a new design pattern called Model-View-ViewModel (MVVM) to evolve. MVVM lets you separate presentation logic from the actual presentation. Basically, it means for the most part you can avoid writing code in the view’s codebehind.

This is a major improvement for those interested in developing testable applications. Now instead of having your presentation logic attached to the view’s codebehind, which has its own lifecycle to complicate testing, you can use a plain old CLR object (POCO). View models don’t have the lifecycle constraints that a view does. You can just instantiate a view model in a unit test and test away.

In this article, I’ll take a look at how to approach writing a testable presentation layer for applications using MVVM. To help illustrate my approach, I’ll include sample code from an open source framework I wrote, called Charmed, and an accompanying sample app, called Charmed Reader. The framework and sample apps are available on GitHub at github.com/brentedwards/Charmed.

I introduced the Charmed framework in my July 2013 article ( msdn.microsoft.com/magazine/dn296512) as a Windows 8 framework and sample application. Then in my September 2013 article ( msdn.microsoft.com/magazine/dn385706), I discussed making it cross-­platform as a Windows 8 and Windows Phone 8 framework and sample app. In both of those articles, I talked about decisions I made to keep the app testable. Now, I’ll revisit those decisions and show how I actually go about testing the app. This article uses Windows 8 and Windows Phone 8 code for the examples, but you can apply the concepts and techniques to any type of application.

About the Sample App

The sample app that illustrates how I approach writing a testable presentation layer is called Charmed Reader. Charmed Reader is a simple blog reader app that works on both Windows 8 and Windows Phone 8. It has the minimum functionality needed to illustrate the key points I want to cover. It’s cross-platform and works mostly the same on both platforms, with the exception that the Windows 8 app leverages some Windows 8-specific functionality. While the app is basic, there’s enough functionality for unit testing.

What Is Unit Testing?

The idea behind unit testing is to take discrete chunks of code (units) and write test methods that use the code in an expected way, then test to see they get the expected results. This test code is run using some sort of test harness framework. There are several test harness frameworks that work with Visual Studio 2012. In the sample code, I use MSTest, which is built into Visual Studio 2012 (and earlier). The goal is to have a single unit test method target a specific scenario. Sometimes it takes several unit test methods to cover all the scenarios you expect your method or property to accomodate.

A unit test method should follow a consistent format to make it easier for other developers to understand. The following format is generally considered a best practice:

  1. Arrange
  2. Act
  3. Assert

First, there may be some setup code you need to write to create an instance of the class under test as well as any dependencies it might have. This is the Arrange section of the unit test.

After the unit test is done setting the stage for the actual test, you can execute the method or property in question. This is the Act section of the test. You can execute the method or property in question with parameters (when applicable) set up during the Arrange section.

Finally, when you’ve executed the method or property in question, the test needs to verify the method or property did exactly what it was supposed to do. This is the Assert section of the test. During the assert phase, assert methods are called to compare actual results with expected results. If the actual results are as expected, the unit test passes. If not, the test fails.

Following this best practice format, my tests usually look something like the following:

[TestMethod]
public void SomeTestMethod()
{
  // Arrange
  // *Insert code to set up test
  // Act
  // *Insert code to call the method or property under test
  // Assert
  // *Insert code to verify the test completed as expected
}

Some people use this format without including comments to call out the different sections of the test (Arrange/Act/Assert). I prefer to have comments separating the three sections just to make sure I don’t lose track of what a test is actually acting on or when I’m just setting up.

An added benefit of having a comprehensive suite of well-written unit tests is that they act as living documentation of the app. New developers viewing your code will be able to see how you expected the code to be used by looking at the different scenarios the unit tests cover.

Planning for Testability

If you want to write a testable application, it really helps to plan ahead. You’ll want to design your application’s architecture so it’s conducive to unit testing. Static methods, sealed classes, database access, and Web service calls all can make your app difficult or impossible to unit test. However, with some planning, you can minimize the impact they have on your application.

The Charmed Reader app is all about reading blog posts. Downloading these blog posts involves Web access to RSS feeds, and it can be rather difficult to unit test that functionality. First, you should be able to run unit tests quickly and in a disconnected state. Relying on Web access in a unit test potentially violates these principles.

Moreover, a unit test should be repeatable. Because blogs are usually updated regularly, it might become impossible to get the same data downloaded over time. I knew in advance that unit testing the functionality that loads blog posts would be impossible if I didn’t plan ahead.

Here’s what I knew needed to happen:

  1. The MainViewModel needed to load all the blog posts the user wants to read at once.
  2. Those blog posts needed to be downloaded from the various RSS feeds the user has saved.
  3. Once downloaded, the blog posts needed to be parsed into data transfer objects (DTOs) and made available to the view.

If I put the code to download the RSS feeds in MainViewModel, it would suddenly be responsible for more than just loading data and letting the view databind to it for display. MainViewModel would then be responsible for making Web requests and parsing XML data. What I really want is to have MainViewModel call out to a helper to make the Web request and parse the XML data. MainViewModel should then be given instances of objects that represent the blog posts to be displayed. These are called DTOs.

Knowing this, I can abstract the RSS feed loading and parsing into a helper object that MainViewModel can call. This isn’t the end of the story, however. If I just create a helper class that does the RSS feed data work, any unit test I write for MainViewModel around this functionality would also end up calling that helper class to do the Web access. As I mentioned before, that goes against the goal of unit testing. So, I need to take it a step further.

If I create an interface for the RSS feed data-loading functionality, I can have my view model work with the interface instead of a concrete class. Then I can provide different implementations of the interface for when I’m running unit tests instead of running the app. This is the concept behind mocking. When I run the app for real, I want the real object that loads the real RSS feed data. When I run the unit tests, I want a mock object that just pretends to load the RSS data, but never actually goes out to the Web. The mock object can create consistent data that is repeatable and never changes. Then, my unit tests can know exactly what to expect every time.

With this in mind, my interface for loading the blog posts looks like this:

public interface IRssFeedService
{
  Task<List<FeedData>> GetFeedsAsync();
}

There’s only one method, GetFeedsAsync, which MainViewModel can use to load the blog post data. MainViewModel doesn’t need to care how IRssFeedService loads the data or how it parses the data. All MainViewModel needs to care about is that calling GetFeedsAsync will asynchronously return blog post data. This is especially important given the cross-platform nature of the app.

Windows 8 and Windows Phone 8 have different ways of downloading and parsing the RSS feed data. By making the IRssFeedService interface and having MainViewModel interact with it, instead of directly downloading blog feeds, I avoid forcing MainViewModel to have multiple implementations of the same functionality.

Using dependency injection, I can make sure to give MainViewModel the correct instance of IRssFeedService at the right time. As I mentioned, I’ll provide a mock instance of IRssFeedService during the unit tests. One interesting thing about using Windows 8 and Windows Phone 8 code as the foundation for a unit test discussion is there aren’t any real dynamic mocking frameworks currently available for those platforms. Because mocking is a big part of how I unit test my code, I had to come up with my own simple way to create mocks. The resulting RssFeedServiceMock is shown in Figure 1.

Figure 1 RssFeedServiceMock

public class RssFeedServiceMock : IRssFeedService
{
  public Func<List<FeedData>> GetFeedsAsyncDelegate { get; set; }
  public Task<List<FeedData>> GetFeedsAsync()
  {
    if (this.GetFeedsAsyncDelegate != null)
    {
      return Task.FromResult<List<FeedData>>(this.GetFeedsAsyncDelegate());
    }
    else
    {
      return Task.FromResult<List<FeedData>>(null);
    }
  }
}

Basically, I want to be able to provide a delegate that can set up how the data is loaded. If you aren’t developing for Windows 8 or Windows Phone 8, there’s a pretty good chance you can use a dynamic mocking framework such as Moq, Rhino Mocks or NSubstitute. Whether you roll your own mocks or use a dynamic mocking framework, the same principles apply.

Now that I have the IRssFeedService interface created and injected into the MainViewModel, the MainViewModel calling GetFeedsAsync on the IRssFeedService interface, and the RssFeed­ServiceMock created and ready to use, it’s time to unit test the MainViewModel’s interaction with IRssFeedService. The important aspects I want to test for in this interaction are that MainViewModel correctly calls GetFeedsAsync and the feed data that’s returned is the same feed data that MainViewModel makes available via the FeedData property. The unit test in Figure 2 verifies this for me.

Figure 2 Testing Feed Loading Functionality

[TestMethod]
public void FeedData()
{
  // Arrange
  var viewModel = GetViewModel();
  var expectedFeedData = new List<FeedData>();
  this.RssFeedService.GetFeedsAsyncDelegate = () =>
    {
      return expectedFeedData;
    };
  // Act
  var actualFeedData = viewModel.FeedData;
  // Assert
  Assert.AreSame(expectedFeedData, actualFeedData);
}

Whenever I’m unit testing a view model (or any other object, for that matter), I like to have a helper method that gives me the actual instance of the view model to test. View models are likely to change over time, which may involve different things being injected into the view model, which means different constructor parameters. If I create a new instance of the view model in all my unit tests and then change the constructor’s signature, I have to change a whole bunch of unit tests along with it. However, if I create a helper method to create that new instance of the view model, I only have to make the change in one place. In this case, GetViewModel is the helper method:

private MainViewModel GetViewModel()
{
  return new MainViewModel(this.RssFeedService, 
    this.Navigator, this.MessageBus);
}

I also use the TestInitialize attribute to ensure the MainViewModel dependencies are created anew before running every test. Here’s the TestInitialize method that makes this happen:

[TestInitialize]
public void Init()
{
  this.RssFeedService = new RssFeedServiceMock();
  this.Navigator = new NavigatorMock();
  this.MessageBus = new MessageBusMock();
}

With this, every unit test in this test class will have brand-new instances of all the mocks when they run.

Looking back at the test itself, the following code creates my expected feed data and sets up the mock RSS feed service to return it:

var expectedFeedData = new List<FeedData>();
this.RssFeedService.GetFeedsAsyncDelegate = () =>
  {
    return expectedFeedData;
  };

Notice I’m not adding any actual FeedData instances to the list of expectedFeedData because I don’t need to. I only need to ensure the list itself is what MainViewModel ends up with. I don’t care what happens when that list actually has FeedData instances in it, at least for this test.

The Act portion of the test has the following line:

var actualFeedData = viewModel.FeedData;

I can then assert the actualFeedData is the same instance of the expectedFeedData. If they aren’t the same instance, then MainViewModel didn’t do its job and the unit test should fail.

Assert.AreSame(expectedFeedData, actualFeedData);

Testable Navigation

Another important piece of the sample application I want to test is navigation. The Charmed Reader sample app uses view model-based navigation because I want to keep the views and view models separate. Charmed Reader is a cross-platform application and the view models I create are used on both platforms, although the views need to be different for Windows 8 and Windows Phone 8. There are a number of reasons why, but it boils down to the fact that each platform has slightly different XAML. Because of this, I didn’t want my view models to know about my views, muddying up the waters.

Abstracting the navigation functionality behind an interface was the solution for several reasons. The first and foremost is that each platform has different classes involved in navigation, and I didn’t want my view model to have to worry about these differences. Also, in both cases, the classes involved in navigation can’t be mocked. So, I abstracted those issues away from the view model and created the INavigator interface:

public interface INavigator
{
  bool CanGoBack { get; }
  void GoBack();
  void NavigateToViewModel<TViewModel>(object parameter = null);
#if WINDOWS_PHONE
  void RemoveBackEntry();
#endif // WINDOWS_PHONE
}

I inject INavigator into the MainViewModel via the constructor, and MainViewModel uses INavigator in a method called ViewFeed:

public void ViewFeed(FeedItem feedItem)
{
  this.navigator.NavigateToViewModel<FeedItemViewModel>(feedItem);
}

When I look at how ViewFeed interacts with INavigator, I see two things I want to verify as I write the unit test:

  1. The FeedItem that’s passed into ViewFeed is the same FeedItem passed into NavigateToViewModel.
  2. The view model type passed to NavigateToViewModel is FeedItemViewModel.

Before I actually write the test, I need to create another mock, this time for INavigator. Figure 3 shows the mock for INavigator. I followed the same pattern as before with delegates for each method as a way to execute test code when the actual method gets called. Again, if you’re working on a platform with mocking framework support, you don’t need to create your own mock.

Figure 3 Mock for INavigator

public class NavigatorMock : INavigator
{
  public bool CanGoBack { get; set; }
  public Action GoBackDelegate { get; set; }
  public void GoBack()
  {
    if (this.GoBackDelegate != null)
    {
      this.GoBackDelegate();
    }
  }
  public Action<Type, object> NavigateToViewModelDelegate { get; set; }
  public void NavigateToViewModel<TViewModel>(object parameter = null)
  {
    if (this.NavigateToViewModelDelegate != null)
    {
      this.NavigateToViewModelDelegate(typeof(TViewModel), parameter);
    }
  }
#if WINDOWS_PHONE
  public Action RemoveBackEntryDelegate { get; set; }
  public void RemoveBackEntry()
  {
    if (this.RemoveBackEntryDelegate != null)
    {
      this.RemoveBackEntryDelegate();
    }
  }
#endif // WINDOWS_PHONE
}

With my mock Navigator class in place, I can put it to use in a unit test, as shown in Figure 4.

Figure 4 Testing Navigation Using the Mock Navigator

[TestMethod]
public void ViewFeed()
{
  // Arrange
  var viewModel = this.GetViewModel();
  var expectedFeedItem = new FeedItem();
  Type actualViewModelType = null;
  FeedItem actualFeedItem = null;
  this.Navigator.NavigateToViewModelDelegate = (viewModelType, parameter) =>
    {
      actualViewModelType = viewModelType;
      actualFeedItem = parameter as FeedItem;
    };
  // Act
  viewModel.ViewFeed(expectedFeedItem);
  // Assert
  Assert.AreSame(expectedFeedItem, actualFeedItem, "FeedItem");
  Assert.AreEqual(typeof(FeedItemViewModel), 
    actualViewModelType, "ViewModel Type");
}

What this test really cares about is that the FeedItem passed around is correct and the view model being navigated to is correct. When working with mocks, it’s important to keep in mind what you should care about for a particular test and what you aren’t concerned with. For this test, because I have the INavigator interface that MainViewModel is working against, I don’t need to care about whether the navigation actually takes place. That’s handled by whatever implements INavigator for the runtime instance. I just need to care that INavigator is given the proper parameters when navigation occurs.

Testable Secondary Tiles

The final area for which I’m going to look at testing is secondary tiles. Secondary tiles are available in both Windows 8 and Windows Phone 8, and they let users pin elements of an app to their home screens, creating a deep link into a specific part of the app. However, secondary tiles are handled completely differently in the two platforms, which means I have to provide platform-specific implementations. Despite the difference, I’m able to provide a consistent interface for secondary tiles I can use on both platforms:

public interface ISecondaryPinner
{
  Task<bool> Pin(TileInfo tileInfo);
  Task<bool> Unpin(TileInfo tileInfo);
  bool IsPinned(string tileId);
}

The TileInfo class is a DTO with properties for both platforms combined to create a secondary tile. Because each platform uses a different combination of properties from TileInfo, each platform needs to be tested differently. I’ll look specifically at the Windows 8 version. Figure 5 shows how my view model uses ISecondaryPinner.

There are actually two things going on in the Pin method in Figure 5. The first is the actual pinning of the secondary tile. The second is saving the FeedItem into local storage. So, that’s two things I need to test. Because this method changes the IsFeedItemPinned property on the view model based on the results of attempting to pin the FeedItem, I also need to test the two possible results of the Pin method on ISecondaryPinner: true and false. Figure 6 shows the first test I implemented, which tests the success scenario.

Figure 5 Using ISecondaryPinner

public async Task Pin(Windows.UI.Xaml.FrameworkElement anchorElement)
{
  // Pin the feed item, then save it locally to make sure it's still available
  // when they return.
  var tileInfo = new TileInfo(
    this.FormatSecondaryTileId(),
    this.FeedItem.Title,
    this.FeedItem.Title,
    Windows.UI.StartScreen.TileOptions.ShowNameOnLogo |
      Windows.UI.StartScreen.TileOptions.ShowNameOnWideLogo,
    new Uri("ms-appx:///Assets/Logo.png"),
    new Uri("ms-appx:///Assets/WideLogo.png"),
    anchorElement,
    Windows.UI.Popups.Placement.Above,
    this.FeedItem.Id.ToString());
  this.IsFeedItemPinned = await this.secondaryPinner.Pin(tileInfo);
  if (this.IsFeedItemPinned)
  {
    await SavePinnedFeedItem();
  }
}

Figure 6 Testing for a Successful Pin

[TestMethod]
public async Task Pin_PinSucceeded()
{
  // Arrange
  var viewModel = GetViewModel();
  var feedItem = new FeedItem
  {
    Title = Guid.NewGuid().ToString(),
    Author = Guid.NewGuid().ToString(),
    Link = new Uri("http://www.bing.com")
  };
  viewModel.LoadState(feedItem, null);
  Placement actualPlacement = Placement.Default;
  TileInfo actualTileInfo = null;
  SecondaryPinner.PinDelegate = (tileInfo) =>
    {
      actualPlacement = tileInfo.RequestPlacement;
      actualTileInfo = tileInfo;
      return true;
    };
  string actualKey = null;
  List<FeedItem> actualPinnedFeedItems = null;
  Storage.SaveAsyncDelegate = (key, value) =>
    {
      actualKey = key;
      actualPinnedFeedItems = (List<FeedItem>)value;
    };
  // Act
  await viewModel.Pin(null);
  // Assert
  Assert.AreEqual(Placement.Above, actualPlacement, "Placement");
  Assert.AreEqual(string.Format(Constants.SecondaryIdFormat,
    viewModel.FeedItem.Id), actualTileInfo.TileId, "Tile Info Tile Id");
  Assert.AreEqual(viewModel.FeedItem.Title,
    actualTileInfo.DisplayName, "Tile Info Display Name");
  Assert.AreEqual(viewModel.FeedItem.Title,
    actualTileInfo.ShortName, "Tile Info Short Name");
  Assert.AreEqual(viewModel.FeedItem.Id.ToString(),
    actualTileInfo.Arguments, "Tile Info Arguments");
  Assert.AreEqual(Constants.PinnedFeedItemsKey, actualKey, "Save Key");
  Assert.IsNotNull(actualPinnedFeedItems, "Pinned Feed Items");
}

There’s a little more setup involved with this than with previous tests. First, after the controller, I set up a FeedItem instance. Notice that I call ToString on Guids for both Title and Author. That’s because I don’t care what the actual values are, I just care that they have values I can compare with in the assert section. Because Link is a Uri, I do need a valid Uri for this to work, so I provided one. Again, what the actual Uri is doesn’t matter, just that it’s valid. The remainder of the setup involves ensuring I capture the interactions for pinning and saving for comparison in the assert section. The key to ensuring this code actually tests the success scenario is that the PinDelegate returns true, indicating success.

Figure 7 shows pretty much the same test, but for the unsuccessful scenario. The fact that PinDelegate returns false is what ensures the test is focusing on the unsuccessful scenario. In the unsuccessful scenario, I also need to verify in the assert section that SaveAsync wasn’t called.

Figure 7 Testing for an Unsuccessful Pin

[TestMethod]
public async Task Pin_PinNotSucceeded()s
{
  // Arrange
  var viewModel = GetViewModel();
  var feedItem = new FeedItem
  {
    Title = Guid.NewGuid().ToString(),
    Author = Guid.NewGuid().ToString(),
    Link = new Uri("http://www.bing.com")
  };
  viewModel.LoadState(feedItem, null);
  Placement actualPlacement = Placement.Default;
  TileInfo actualTileInfo = null;
  SecondaryPinner.PinDelegate = (tileInfo) =>
  {
    actualPlacement = tileInfo.RequestPlacement;
    actualTileInfo = tileInfo;
    return false;
  };
  var wasSaveCalled = false;
  Storage.SaveAsyncDelegate = (key, value) =>
  {
    wasSaveCalled = true;
  };
  // Act
  await viewModel.Pin(null);
  // Assert
  Assert.AreEqual(Placement.Above, actualPlacement, "Placement");
  Assert.AreEqual(string.Format(Constants.SecondaryIdFormat,
    viewModel.FeedItem.Id), actualTileInfo.TileId, "Tile Info Tile Id");
  Assert.AreEqual(viewModel.FeedItem.Title, actualTileInfo.DisplayName,
    "Tile Info Display Name");
  Assert.AreEqual(viewModel.FeedItem.Title, actualTileInfo.ShortName,
    "Tile Info Short Name");
  Assert.AreEqual(viewModel.FeedItem.Id.ToString(),
    actualTileInfo.Arguments, "Tile Info Arguments");
  Assert.IsFalse(wasSaveCalled, "Was Save Called");
}

Writing testable applications is a challenge. It’s especially challenging to test the presentation layer where user interaction is involved. Knowing in advance that you’re going to write a testable application lets you make decisions at every step in favor of testability. You can also look out for things that will make your app less testable and come up with ways to fix them.

Over the course of three articles, I’ve discussed writing testable apps with the MVVM pattern, specifically for Windows 8 and Windows Phone 8. In the first article, I looked at writing testable Windows 8 applications, while still leveraging Windows 8-specific features not easily testable by themselves. The second article evolved the discussion to include developing testable cross-platform apps with Windows 8 and Windows Phone 8. With this article, I showed how I approach testing the apps I worked so hard to make testable in the first place.

MVVM is a broad topic, with a number of different interpretations. I’m glad to have been able to share my interpretation of such an interesting topic. I find a lot of value in using MVVM, especially as it relates to testability. I also find exploring testability to be stimulating and useful, and I’m glad to share my approach to writing a testable application.


Brent Edwards is a principal lead consultant for Magenic, a custom application development firm that focuses on the Microsoft stack and mobile application development. He is also a cofounder of the Twin Cities Windows 8 User Group in Minneapolis. Reach him at brente@magenic.com.

THANKS to the following technical expert for reviewing this article: Jason Bock (Magenic)
Jason Bock is a Practice Lead with Magenic ( www.magenic.com). He’s also the co-author of Metaprogramming in .NET ( www.manning.com/hazzard). Reach him at jasonbock.net or on twitter: @jasonbock.

 

Rate: