How to Write Unit Tests for ViewModels

Unit testing of viewmodels in MugenMvvm is pretty straightforward.

There is some base test class in MugenMvvm: UnitTestBase, it contains code to initialize test fixture and fake implementations of App, GetViewModel and so on.

If you want to start write unit tests for your viewmodels you have to inherit this base class and call Initialize method:

public abstract class UnitTestBase
{
  #region Nested Types

    public sealed class DefaultUnitTestModule : InitializationModuleBase
    {
      #region Methods

        public override bool Load(IModuleContext context)
      {
        return context.IsSupported(LoadMode.UnitTest) && base.Load(context);
      }

      #endregion
    }

  protected class UnitTestApp : MvvmApplication
  {
    #region Fields

      private readonly IModule[] _modules;

    #endregion

      #region Constructors

      public UnitTestApp(LoadMode mode = LoadMode.UnitTest, params IModule[] modules)
      : base(mode)
      {
        _modules = modules;
      }

    #endregion

      #region Methods

      protected override IList<IModule> GetModules(IList<Assembly> assemblies)
    {
      if (_modules.IsNullOrEmpty())
        return base.GetModules(assemblies);
      return _modules;
    }

    protected override IModuleContext CreateModuleContext(IList<Assembly> assemblies)
    {
      return new ModuleContext(PlatformInfo.UnitTest, LoadMode.UnitTest, IocContainer, null, assemblies);
    }

    public override Type GetStartViewModelType()
    {
      return typeof(IViewModel);
    }

    #endregion
  }

  #endregion

    #region Properties

    protected IIocContainer IocContainer => ServiceProvider.IocContainer;

  protected IViewModelProvider ViewModelProvider { get; set; }

  #endregion

    #region Methods

    protected void Initialize([NotNull] IIocContainer iocContainer, params IModule[] modules)
  {
    Initialize(iocContainer, PlatformInfo.UnitTest, modules);
  }

  protected void Initialize([NotNull] IIocContainer iocContainer, PlatformInfo platform, params IModule[] modules)
  {
    Initialize(new UnitTestApp(modules: modules), iocContainer, platform, typeof(UnitTestApp).GetAssembly(),
               GetType().GetAssembly());
  }

  protected void Initialize([NotNull] IMvvmApplication application, [NotNull] IIocContainer iocContainer,
                            params Assembly[] assemblies)
  {
    Initialize(application, iocContainer, PlatformInfo.UnitTest, assemblies);
  }

  protected void Initialize([NotNull] IMvvmApplication application, [NotNull] IIocContainer iocContainer,
                            PlatformInfo platform, params Assembly[] assemblies)
  {
    Should.NotBeNull(application, nameof(application));
    Should.NotBeNull(iocContainer, nameof(iocContainer));
    application.Initialize(platform ?? PlatformInfo.UnitTest, iocContainer, assemblies, DataContext.Empty);
    if (ViewModelProvider == null)
    {
      IViewModelProvider viewModelProvider;
      ViewModelProvider = iocContainer.TryGet(out viewModelProvider) ? viewModelProvider : new ViewModelProvider(iocContainer);
    }
  }

  protected internal IViewModel GetViewModel([NotNull] GetViewModelDelegate<IViewModel> getViewModel,
                                             IViewModel parentViewModel = null, ObservationMode? observationMode = null, params DataConstantValue[] parameters)
  {
    return ViewModelProvider.GetViewModel(getViewModel, parentViewModel, observationMode, parameters);
  }

  protected internal T GetViewModel<T>([NotNull] GetViewModelDelegate<T> getViewModelGeneric,
                                       IViewModel parentViewModel = null, ObservationMode? observationMode = null, params DataConstantValue[] parameters)
    where T : class, IViewModel
    {
      return ViewModelProvider.GetViewModel(getViewModelGeneric, parentViewModel, observationMode, parameters);
    }

  protected internal IViewModel GetViewModel([NotNull] Type viewModelType,
                                             IViewModel parentViewModel = null, ObservationMode? observationMode = null, params DataConstantValue[] parameters)
  {
    return ViewModelProvider.GetViewModel(viewModelType, parentViewModel, observationMode, parameters);
  }

  protected internal T GetViewModel<T>(IViewModel parentViewModel = null, ObservationMode? observationMode = null, params DataConstantValue[] parameters)
    where T : IViewModel
    {
      return ViewModelProvider.GetViewModel<T>(parentViewModel, observationMode, parameters);
    }

  #endregion
}

There are several overloads of Initialize methods, all of them has self-describing parameters.

Let's take an simple app and write some unit tests for it.

We are going to use NUnit for unit tests and Moq for mocking, so basic knowledge of unit testing, mocking and libs (NUnit, Moq) is required.

As our application we will take our HelloUnitTests app. Clone it on your PC and open the solution. Restore all nuget packages in HelloUnitTests. In order to restore Net Standard libs in "Core" project try to reinstall them.

19201920

Let's create some unit tests for our MainViewModel.

First of all, create MainViewModelTests class in "HelloUnitTests.Tests" project. Inherits it by "UnitTestBase" and then add there the code presented below:

[SetUp]
public void SetUp()
{
  _viewModelPresenterMock = new Mock<IViewModelPresenter>();

  _serializer = new Serializer(AppDomain.CurrentDomain.GetAssemblies());

  var container = new AutofacContainer();
  container.BindToConstant(_viewModelPresenterMock.Object);

  Initialize(container, new DefaultUnitTestModule());

  ApplicationSettings.CommandExecutionMode = CommandExecutionMode.None;
}

private Mock<IViewModelPresenter> _viewModelPresenterMock;
private ISerializer _serializer;

As result you will get something similar to:

19171917

MainViewModelTests class

Here we marked MainViewModelTests as test fixture and added SetUp method. In this method we created a mock of IViewModelPresenter and register it in IoC container. After that we called Initialize method of UnitTestBase and set command execution mode in CommandExecutionMode.None state. Also we initialized ISerializer interface in order to use it in test that checks correctness of state saving.

📘

Remarks about SetUp method

We have to mock IViewModelPresenter in order to make GetViewModel method work.

Also, we set command execution mode of ApplicationSettings.CommandExecutionMode to None state in order to make commands executed without usage of ThreadManager.

Now, when all preparations are done let's create some unit tests. Let's start from trivial cases.

Our app allows create users and save them in memory as long as delete them. So, we have two commands:

  1. CreateUserCommand
  2. DeleteUserCommand
    And one inner viewmodel for work with grid: GridViewModel:
public class MainViewModel : EditableViewModel<User>
 {
   private GridViewModel<User> _userGridViewModel;

   public ICommand AddUserCommand { get; private set; }
   public ICommand DeleteUserCommand { get; private set; }
  
   // some other code
 }

So our first test will be pretty simple. Let's just test that commands and inner view model is initialized:

[Test]
public void VmShouldInitializeCommandsAndUserGridViewModel()
{
  var viewModel = GetViewModel<MainViewModel>();
  Assert.IsNotNull(viewModel.AddUserCommand, "AddUserCommand is null");
  Assert.IsNotNull(viewModel.DeleteUserCommand, "DeleteUserCommand is null");

  Assert.IsNotNull(viewModel.UserGridViewModel, "UserGridViewModel is null");
}

We just create our viewmodel and tested it against null.

Now create a test that checks initial state of commands and it's behaviour when SelectedItem is changing:

[Test]
public void AddUserCmdCanBeExecutedAlways()
{
  var viewModel = GetViewModel<MainViewModel>();
  Assert.IsTrue(viewModel.AddUserCommand.CanExecute(null));

  var user = new User
  {
    Firstname = "TestFirstname",
    Lastname = "TestLastname"
  };
  viewModel.UserGridViewModel.ItemsSource.Add(user);
  viewModel.UserGridViewModel.SelectedItem = user;

  Assert.IsTrue(viewModel.AddUserCommand.CanExecute(null));
}

[Test]
public void DeleteCmdCanBeExecutedSelectedItemNotNull()
{
  var viewModel = GetViewModel<MainViewModel>();

  var user = new User
  {
    Firstname = "TestFirstname",
    Lastname = "TestLastname"
  };
  viewModel.UserGridViewModel.ItemsSource.Add(user);
  viewModel.UserGridViewModel.SelectedItem = user;

  Assert.IsTrue(viewModel.DeleteUserCommand.CanExecute(null));
}

[Test]
public void DeleteCmdCannotBeExecutedSelectedItemNull()
{
  var viewModel = GetViewModel<MainViewModel>();

  Assert.IsNull(viewModel.UserGridViewModel.SelectedItem);
  Assert.IsFalse(viewModel.DeleteUserCommand.CanExecute(null));
}

Let's take some more complex examples.

Save State Test

In this test we are going to check that viewmodel save it's state correctly.
Our viewmodel MainViewModel implements IHasState interface and can save it's state correctly. Let's check this behaviour.

Add method that can create a deep copy of object using serialization:

private TState UpdateState<TState>(TState state)
  where TState : IDataContext
  {
    var stream = _serializer.Serialize(state);
    stream.Position = 0;
    return (TState)_serializer.Deserialize(stream);
  }

And now let's add unit test:

[Test]
public void VmShouldSaveAndRestoreState()
{
  var model = new User
  {
    Firstname = "TestFirstname",
    Lastname = "TestLastname"
  };

  var viewModel = GetViewModel<MainViewModel>();
  viewModel.InitializeEntity(model, false);

  var state = new DataContext();
  viewModel.SaveState(state);
  state = UpdateState(state);

  viewModel = GetViewModel<MainViewModel>();
  viewModel.LoadState(state);

  Assert.IsTrue(viewModel.IsEntityInitialized);
  Assert.IsFalse(viewModel.IsNewRecord);
  Assert.AreEqual(viewModel.Lastname, model.Lastname);
  Assert.AreEqual(viewModel.Firstname, model.Firstname);
}

Here we created entity and pushed in our viewmodel instance. After that we simulated recovering from a tombstoned state (13-15 lines) and reinitialize new instance of viewmodel by saved state. After that we just made some assertions for the fields.

Presented below is full list of our unit tests:

using System;
using Core.Models;
using Core.ViewModels;
using Moq;
using MugenMvvmToolkit;
using MugenMvvmToolkit.Infrastructure;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Interfaces.Models;
using MugenMvvmToolkit.Interfaces.Presenters;
using MugenMvvmToolkit.Models;
using NUnit.Framework;

namespace HelloUnitTests.Tests
{
    [TestFixture]
    public class MainViewModelTests : UnitTestBase
    {
        [SetUp]
        public void SetUp()
        {
            _viewModelPresenterMock = new Mock<IViewModelPresenter>();

            _serializer = new Serializer(AppDomain.CurrentDomain.GetAssemblies());

            var container = new AutofacContainer();
            container.BindToConstant(_viewModelPresenterMock.Object);

            Initialize(container, new DefaultUnitTestModule());

            ApplicationSettings.CommandExecutionMode = CommandExecutionMode.None;
        }

        private Mock<IViewModelPresenter> _viewModelPresenterMock;
        private ISerializer _serializer;

        private TState UpdateState<TState>(TState state)
            where TState : IDataContext
        {
            var stream = _serializer.Serialize(state);
            stream.Position = 0;
            return (TState)_serializer.Deserialize(stream);
        }

        [Test]
        public void AddUserCmdCanBeExecutedAlways()
        {
            var viewModel = GetViewModel<MainViewModel>();
            Assert.IsTrue(viewModel.AddUserCommand.CanExecute(null));

            var user = new User
            {
                Firstname = "TestFirstname",
                Lastname = "TestLastname"
            };
            viewModel.UserGridViewModel.ItemsSource.Add(user);
            viewModel.UserGridViewModel.SelectedItem = user;

            Assert.IsTrue(viewModel.AddUserCommand.CanExecute(null));
        }

        [Test]
        public void DeleteCmdCanBeExecutedSelectedItemNotNull()
        {
            var viewModel = GetViewModel<MainViewModel>();

            var user = new User
            {
                Firstname = "TestFirstname",
                Lastname = "TestLastname"
            };
            viewModel.UserGridViewModel.ItemsSource.Add(user);
            viewModel.UserGridViewModel.SelectedItem = user;

            Assert.IsTrue(viewModel.DeleteUserCommand.CanExecute(null));
        }

        [Test]
        public void DeleteCmdCannotBeExecutedSelectedItemNull()
        {
            var viewModel = GetViewModel<MainViewModel>();

            Assert.IsNull(viewModel.UserGridViewModel.SelectedItem);
            Assert.IsFalse(viewModel.DeleteUserCommand.CanExecute(null));
        }

        [Test]
        public void VmShouldInitializeCommandsAndUserGridViewModel()
        {
            var viewModel = GetViewModel<MainViewModel>();
            Assert.IsNotNull(viewModel.AddUserCommand, "AddUserCommand is null");
            Assert.IsNotNull(viewModel.DeleteUserCommand, "DeleteUserCommand is null");

            Assert.IsNotNull(viewModel.UserGridViewModel, "UserGridViewModel is null");
        }

        [Test]
        public void VmShouldSaveAndRestoreState()
        {
            var model = new User
            {
                Firstname = "TestFirstname",
                Lastname = "TestLastname"
            };

            var viewModel = GetViewModel<MainViewModel>();
            viewModel.InitializeEntity(model, false);

            var state = new DataContext();
            viewModel.SaveState(state);
            state = UpdateState(state);

            viewModel = GetViewModel<MainViewModel>();
            viewModel.LoadState(state);

            Assert.IsTrue(viewModel.IsEntityInitialized);
            Assert.IsFalse(viewModel.IsNewRecord);
            Assert.AreEqual(viewModel.Lastname, model.Lastname);
            Assert.AreEqual(viewModel.Firstname, model.Firstname);
        }
    }
}
885885

All tests passed.

You can find another examples of unit testing in MugenMvvm on Github.

The version of "HelloUnitTests" with tests can be found on Github.