Unit Testing the Xamarin Forms Mobile App: The ViewModel

In the previous post I demonstrated unit testing for the Azure Functions that are used by the Xamarin Mobile application for myWineDB (to-date). In this post (and the one to follow) I will document the challenges to writing unit tests for the Mobile application.

I knew when I started that I should have been testing as I went but I was more interested in proving the concept than methodically constructing the features. As a result I found that adjusting the code to support the tests was required. I am by no means an expert tester but, I know enough to understand what code coverage is. Some of the steps I took may not be elegant but, I have 100% code coverage. I hope a more experienced tester will offer up a better solution is some places.

The mobile application for myWineDB GitHubs’ repository is not public and one day I aspire to sell this application. Forgive me if I reserve the right to not expose it publicly. However, I did publish a reduced version of the project for my post …You Get the Horns Part 2, it is called AuthorizationXamarinFormsSample which has a subset of my mobile application and it is public. I will use it for this discussion and I have added the Unit Tests that are appropriate to it there. The Mobile application is built off of the “AppShell” project that comes with recent versions of Visual Studio. There are three types of classes that are potentially candidates for Unit Testing: View, ViewModel, and “Service” classes. Fortunately, my application is pretty simple and I don’t have any custom code for mobile devices (yet). If I did there is potential I would have to Unit Test within the specific native projects. Xamarin has a wonderful UI testing framework that I will use when I finally settle on my UI controls and it should cover any special cases for specific mobile platforms. It is these tests that would cover the View code as the Xamarin Forms UI uses event “Commands” which are basically passed through to the ViewModel class so the “View” classes are pretty much uninteresting and not “unit” testable. The ViewModel classes are another story.

I fell into the trap of writing untestable code and had to spend some time refactoring to make things testable, Starting with the constructor:

// Before
public IAsyncCommand LoadCellarsList { get; private set; }

public CellarViewModel()
{
   CellarListItems = new ObservableCollection<CellarListItem>();
   Title = "Home";
   LoadCellarsList = 
      new AsyncCommand(() => PopulateCellarListAsync(), 
      () => CanExecute());
}
// After
public IAsyncCommand LoadCellarsList { get; private set; }
…
[PreferredConstructor]
public CellarViewModel()
{
   InitializeViewModel(false);
}

public CellarViewModel(bool TestException=false, IWineStore Store = null)
{
   this.WineStore = Store ?? this.WineStore;
#if DEBUG
   InitializeViewModel(TestException);
#else
   InitializeViewModel(false);
#endif           
}

Compiler directives? Really…! I wish I knew a better way to handle this. Long term this code is not viable for a CI/CD pipleline but fortunately I found a work around at https://stackoverflow.com/questions/24157714/how-to-append-conditional-compilation-symbols-in-project-properties-with-msbuild

When (or if) it comes time to create a CI/CD pipeline I can create a compiler directive symbol (say Test) and I can set that symbol in an MSBuild statement and add another if condition to the existing logic. That way the Test server can build a Release and test it and if it passes it can be built again without that symbol effectively bypassing the parameters needed for testing. Looking ahead briefly, we need to be able to inject an exception to see how the method under test would react to one. And we need to inject a mock service class in order to return data to the method without actually using the service. Since the new constructor is a public method we sure don’t want a rouge actor using it to game/hack the real application. The compiler directives will effectively by-pass the parameters of this constructor in a production environment (e.g., my phone). Line 4 of the “After” code is needed for the dependency injection class “SimpleIoc” in the MVVMLight framework I am using. To support the new constructor an additional property was added to support the injected value (ThrowExceptionFlag) and the Base class for the View Models was modified to allow the replacement of the WineStore property with a Mock.

With these changes in place we can test the method (albeit with additional modifications).

public async Task PopulateCellarListAsync()
{
   try
   {
      if (ThrowExceptionFlag)
      {
         throw new Exception();
      }
      IsBusy = true;
      var listCellarSummaryModel = await WineStore.GetCellarsAsync();
      var enumCellarListItems = ((from c in listCellarSummaryModel
             select new CellarListItem()
              {
                 Text = $"{c.Name}" + $" - Bottles: {c.BottleCount}" +
                        (c.Capacity > 0 ? $" - % Capacity: 
                           {((c.BottleCount / c.Capacity) * 100):P}"
                           : string.Empty),
                 Key = c.CellarId,

              }).AsEnumerable()).ToList();

      CellarListItems = 
         new ObservableCollection<CellarListItem>(enumCellarListItems);
   }
   catch
   {
     _selectedCellarListItem = 
         new CellarListItem() {Text = "Exception Occurred"};
   }
   finally
   {
      if (CellarListItems.Count > 0)
      {
         SelectedCellarListItem = CellarListItems.First();
      }
      IsBusy = false;
   }
}

One might argue that using try-catch blocks is bad code but this is a UI application on a phone. The alternative is to use logging but I am already taking up a lot of space with the cellar information so I am opting for the try-catch approach. Looking at the code, I don’t see how an exception could occur but, one never knows and while my test throws a basic exception, who knows what kind of exception would actually get thrown? So for now I simply set the Selected Cellar List Item Text to “Exception Occurred” catch the exception and return, then I test for Selected Cellar List Item Text property.

As I see it there are three possible tests for the Cellar ViewModel (some might argue there are only two as there are only two ways to exit this method); The two “must have” cases are the success case with cellar list items, and the exception case, I added an additional test for the “Empty Cellar” list as well. The unit tests follow:

protected Mock<HttpClient> MockHttpClient { get; private set; }
protected Mock<IWineStore> MockWineStore { get; private set; }

[SetUp]
public void SetUpTest()
{
    MockHttpClient = new Mock<HttpClient>();
    MockWineStore = new Mock<IWineStore>();
}

[Test]
public async Task PopulateCellarListAsync_ReturnsACellarInList_Success()
{
    MockWineStore.Setup(c => c.GetCellarsAsync(false,null,null)).ReturnsAsync(TestValues.CellarSummaryModelListObject);

    var sut = new Mobile.ViewModels.CellarViewModel(false, MockWineStore.Object);
    await sut.PopulateCellarListAsync();

    Assert.AreEqual(1,sut.CellarListItems.Count);
}

[Test]
public async Task PopulateCellarListAsync_ReturnsEmptyCellarList_Success()
{
    MockWineStore.Setup(c => c.GetCellarsAsync(false, null, null)).ReturnsAsync(new ObservableCollection<CellarSummaryModel>());
    var sut = new Mobile.ViewModels.CellarViewModel(false, MockWineStore.Object);
    await sut.PopulateCellarListAsync();

    Assert.AreEqual(0,sut.CellarListItems.Count);
}

[Test]
public async Task PopulateCellarListAsync_ThrowsException()
{
    var sut = new Mobile.ViewModels.CellarViewModel(true, MockWineStore.Object);
    await sut.PopulateCellarListAsync();
    Assert.AreEqual("Exception Occurred", sut.SelectedCellarListItem.Text);
}

The next article will discuss the unit tests for the service class “WineStore”. Feel free to view the project on GitHub. Comments are always appreciated.

Published by Boyd Taylor

I have developed software for over thirty years. I am a husband, father, wine collector, and Microsoft technology bigot.

2 thoughts on “Unit Testing the Xamarin Forms Mobile App: The ViewModel

  1. It should be possible to setup your MockWineStore to throw an exception when GetCellarsAsync() is called. My C# is pretty rusty so I’m not sure if that would work with the async/await stuff, but if it does it would be much cleaner and you could ditch the special flag and the compiler directives.

    Another possibility is to setup the mock’s GetCellarsAsync method to return null. Wouldn’t that make the “from c in …” puke or is there an implicit null check?

    Like

Leave a comment

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