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.
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:
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 makeGetViewModel
method work.Also, we set command execution mode of
ApplicationSettings.CommandExecutionMode
toNone
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:
- CreateUserCommand
- 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);
}
}
}
You can find another examples of unit testing in MugenMvvm on Github.
The version of "HelloUnitTests" with tests can be found on Github.
Updated about 7 years ago