unit-testingninjectxamarin.formsmvvm-light

How to organize code in Xamarin Forms viewmodel for unit testing?


I have an XF project that uses Mvvm Light and Ninject. I am trying to backtrack and write unit tests for my viewmodels but I keep running into issues where Xamarin.Forms.Init() needs to be called or I have references to Device.BeginInvokeOnMainThread or other UI specific stuff in RelayCommands.

So a typical RelayCommand might look like this:

public RelayCommand DeleteGameCommand
{
    get
    {
        return _deleteGameCommand
            ?? (_deleteGameCommand = new RelayCommand(
            async () =>
            {
                Device.BeginInvokeOnMainThread(() => ToggleMasterMenuDisplayCommand.Execute(null));
                var response = await
                    ((MasterDetailPage) Application.Current.MainPage).Detail.DisplayActionSheet("Are you sure?", "Cancel",
                        "Delete Game");
                if (response == "Delete Game")
                {
                    StopTimer();

                    using (new Busy(this, "Deleting Game..."))
                    {
                        await GameService.DeleteGame(Game, true);
                        await AzureService.SyncGameInfoForGame(Game.GameId, true);
                    }

                    App.Current.Container.Get<DashboardViewModel>().ShouldRefreshDataOnNextDisplay();
                    Device.BeginInvokeOnMainThread(() =>
                    {
                        App.Current.HideLoading();
                        LoadHomeAsMainPage();
                    });
                }
            }));
    }
}

Busy() calls some UI stuff to display progress indicators. App.Current.Container is my Ninject kernel.

Should I be looking to refactor this to something more like:

public RelayCommand DeleteGameCommand
{
    get
    {
        return _deleteGameCommand
            ?? (_deleteGameCommand = new RelayCommand(
            async () =>
            {
                Device.BeginInvokeOnMainThread(() => ToggleMasterMenuDisplayCommand.Execute(null));
                var response = await
                    ((MasterDetailPage) Application.Current.MainPage).Detail.DisplayActionSheet("Are you sure?", "Cancel",
                        "Delete Game");
                if (response == "Delete Game")
                {
                    StopTimer();

                    using (new Busy(this, "Deleting Game..."))
                    {
                        await BusinessLogic.DeleteGame(Game,true);
                    }

                    App.Current.Container.Get<DashboardViewModel>().ShouldRefreshDataOnNextDisplay();
                    Device.BeginInvokeOnMainThread(() =>
                    {
                        App.Current.HideLoading();
                        LoadHomeAsMainPage();
                    });
                }
            }));
    }
}



public class BusinessLogic
{
    public async Task DeleteGame(Game game, bool includeRelated)
    {
        await GameService.DeleteGame(game, true);
        await AzureService.SyncGameInfoForGame(game.GameId, true); 
    }
}

I realize this might not be the best example because it is likely that the two service calls are already unit tested, but in other relay commands there is more going on than just calls to services.

I was thinking that if I did something like this then I could unit test all the business logic stuff and never write a test that calls a RelayCommand.

I'm not sure how other people are approaching unit testing view models, most of the info I have been able to find has examples that are so simple they never address the issues I keep running into.


Solution

  • Generally you would create and call a wrapper so that when you have automated tests, these stubs would run instead. Your mocked Init and BeginInvokeOnMainThread might forward to different implementations.