Unit testing Xamarin.Forms ViewModels with Prism Navigation Service
Learning the hard way, again 😓
The problem
Recently I’ve faced a disgusting situation trying to write unit tests for my ViewModel on my current Xamarin.Forms project 😖
I’m using Prism in my project, a MVVM library that I know and love from my previous apps and I use it mainly for navigation handling and dependency injection and resolution. The weird situation is that I’ve been using it for at least 2 years and I didn’t have notice that it’s not possible to mock INavigationService when you use some of NavigateAsync overloads.

You can’t mock NavigateAsync(pageName, params, useModal, animated) because is an extension method. You can check it here.
The solution
My first thought was: “Ok, I’m going to use INavigationService without mocking it. I can check the result looking for in navigation stack”. Wrong. I didn’t think this way Prism navigation service actually navigates so it instances other ViewModels I didn’t want to test. That makes tests fail miserably because I didn’t mock some service that uses platform/XF code.
After a few hours trying other ideas that crossed my mind and failing I finally go for implementing my own PageNavigationService. Still not able to mock it with Moq or similar so I wrote my navigation stacks to store the page names which I pretend to navigate to:
public class PageNavigationServiceMock : INavigationService, IPlatformNavigationService, IPageAware
{
public Page Page { get; set; }
public PageNavigationServiceMock()
{
NavigationUtilities.Init();
}
public Task GoBackAsync()
{
throw new NotImplementedException();
}
public Task GoBackAsync(INavigationParameters parameters)
{
throw new NotImplementedException();
}
public Task NavigateAsync(Uri uri)
{
throw new NotImplementedException();
}
public Task NavigateAsync(Uri uri, INavigationParameters parameters)
{
throw new NotImplementedException();
}
public async Task NavigateAsync(string name)
{
NavigationUtilities.AddPageName(name);
return await Task.FromResult(new NavigationResult());
}
public Task NavigateAsync(string name, INavigationParameters parameters)
{
throw new NotImplementedException();
}
public Task GoBackAsync(INavigationParameters parameters, bool? useModalNavigation, bool animated)
{
throw new NotImplementedException();
}
public Task GoBackToRootAsync(INavigationParameters parameters)
{
throw new NotImplementedException();
}
public async Task NavigateAsync(string name, INavigationParameters parameters, bool? useModalNavigation, bool animated)
{
if (useModalNavigation != null && (bool)useModalNavigation)
{
NavigationUtilities.AddModalPageName(name);
}
else
{
NavigationUtilities.AddPageName(name);
}
return await Task.FromResult(new NavigationResult());
}
public Task NavigateAsync(Uri uri, INavigationParameters parameters, bool? useModalNavigation, bool animated)
{
throw new NotImplementedException();
}
}
NavigationUtilities is a simple class to store page names simulating navigation stacks.
Now we need to override the Prism PageNavigationService with our own implementation:
public class PrismApplicationMock : App
{
public new INavigationService NavigationService => base.NavigationService;
public PrismApplicationMock(IPlatformInitializer platformInitializer) : base(platformInitializer) { }
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
base.RegisterTypes(containerRegistry);
// Override Prism navigation service
containerRegistry.Register(NavigationServiceName);
}
}
public class MockPlatformInitializer : IPlatformInitializer
{
public void RegisterTypes(IContainerRegistry containerRegistry)
{
// Native services
containerRegistry.RegisterSingleton();
}
}
Finally we can write our tests like that:
[TestClass]
public class MainPageViewModelTests
{
private static Mock _authenticationService;
private static MockPlatformInitializer _initializer;
private static PrismApplicationMock _app;
private static MainPageViewModel _mainPageViewModel;
[TestInitialize]
public void Init()
{
// Init XF and Prism mocks
XamarinFormsMock.Init();
_initializer = new MockPlatformInitializer();
_app = new PrismApplicationMock(_initializer);
_authenticationService = new Mock();
_mainPageViewModel = new MainPageViewModel(_app.NavigationService, _authenticationService.Object);
}
[TestCleanup]
public void Cleanup()
{
_authenticationService = null;
_initializer = null;
_app = null;
_mainPageViewModel = null;
}
[TestMethod]
public async Task RefreshSessionAsync_ShouldNavigateToLoginPage()
{
// Arrange
_authenticationService.Setup(x => x.RetrieveTokenAsync()).ReturnsAsync(string.Empty);
// Act
await _mainPageViewModel.RefreshSessionAsync();
// Assert
var lastPageInStack = NavigationUtilities.GetCurrentPageName();
Assert.IsTrue(lastPageInStack == nameof(LoginPage));
}
}
🎉🎉🎉
This solution is a workaround (ñapa in Spanish) and I don’t like it. It becomes a mess if additionally you have an extended INavigationService with your own methods so you need either writing more functionality on NavigationUtilities or mocking your IExtendedNavigationService when you use your own methods and using the PageNavigationServiceMock instance in other cases… What a headache!
Please let me know if you have a better solution. I’ll be very greatful!
Comments
Post a Comment