-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unable to Mock INavigationService #1312
Comments
I should have said before if I try to implement INavigationService on my own custom mock I get the following exception: System.InvalidCastException : Unable to cast object of type 'Minervex.Mocks.NewINavigationServiceMock' to type 'Prism.Navigation.INavigateInternal'. |
You should inherit from the PageNavigationService and override the internal methods. Let me know if this still is a blocker for you. |
I'm running to the same issue where I'm using Moq and use the Verify method on the mocked INavigationService to confirm that the NavigateAsync method was called. This seems to fall over as the navigate method exists in INavigationServiceExtensions and casts the INavigationService object to an INavigateInternal object that is internal to PRISM. Do you have suggestions/solutions on this that would work with Moq? Update: think i figured out how to get around it as you suggested with deriving from the PageNavigationService that would work with Moq. I guess the question is then is if there'd be changes made to the navigation service as using a mocking framework with the INavigationService won't work due to the casting that happens. |
I would tend to imagine that if you update your Navigation calls to no longer specify |
@dansiegel yeah, your answer is exactly right |
Hi MaikuB, Can you please post some sample code how you managed to mock INavigationService ? |
@dansiegel - What if you actually want to specify that you'd like to use modal navigation? I've tried creating my own class inheriting from PageNavigationService, but I'm running into errors with everything I'm trying. If anyone has a sample they could point me to of verifying a call to NavigateAsync(string, INavigationParameters, bool?, bool), I would be greatly appreciative. @MaikuB - Did you have a sample you could possibly share? |
I think my idea didn't end up working in the end (bear in mind this is a while and code belongs to client) so afraid I don't have anything to share. We also had the same problem where there was modal navigation in the app |
I think we can fix this simply by making |
Personally I don't mock INavigationService, and I test by observation: var app = CreateApp();
Assert.IsType<MainPageViewModel>(app.MainPage.BindingContext);
var vm = (MainPageViewModel)app.MainPage.BindingContext;
// Do Stuff
vm.NavigateCommand.Execute()
Assert.IsType<ViewA>(PageUtilities.GetCurrentPage(app.MainPage)); A couple of notes here... I do tend to add a Mock App based on my actual app. As an example one of the things I typically add is a specific logger for tests so I get richer output in my tests. public class XunitLogger : ILoggerFacade
{
private ITestOutputHelper _testOutputHelper { get; }
public XunitLogger(ITestOutputHelper testOutputHelper) =>
_testOutputHelper = testOutputHelper;
public void Log(string message, Category category, Priority priority) =>
_testOutputHelper.WriteLine($"{category} - {priority}: {message}");
}
public class XunitPlatformInitializer : IPlatformInitializer
{
private ITestOutputHelper _testOutputHelper { get; }
public XunitPlatformInitializer(ITestOutputHelper testOutputHelper) =>
_testOutputHelper = testOutputHelper;
public void RegisterTypes(IContainerRegistry containerRegistry) =>
containerRegistry.RegisterInstance(_testOutputHelper);
}
public class AppMock : App
{
public AppMock(IPlatformInitializer platformInitializer) : base(platformInitializer) { }
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// Call the base implementation first so all services are registered.
// Then new registrations can replace existing as needed.
base.RegisterTypes(containerRegistry);
containerRegistry.Register<ILoggerFacade, XunitLogger>();
}
} Putting it all together looks something like: public class SomeFixture : IDisposable
{
private ITestOutputHelper _testOutputHelper { get; }
public SomeFixture(ITestOutputHelper testOutputHelper)
{
_testOutputHelper = testOutputHelper;
Xamarin.Forms.Mocks.MockForms.Init();
}
private AppMock CreateApp()
{
var initializer = new XunitPlatformInitializer(_testOutputHelper);
return new AppMock(initializer);
}
[Fact]
public void DoesNavigateToViewA()
{
var app = CreateApp();
Assert.IsType<MainPageViewModel>(app.MainPage.BindingContext);
var vm = (MainPageViewModel)app.MainPage.BindingContext;
// Do Stuff
vm.NavigateCommand.Execute()
Assert.IsType<ViewA>(PageUtilities.GetCurrentPage(app.MainPage));
}
public void Dispose()
{
PageNavigationRegistry.ClearRegistrationCache();
}
} |
Thanks so much for the replies @brianlagunas, @dansiegel, and @MaikuB... I greatly appreciate it! @MaikuB - Did you just wind up not testing navigation calls any longer? @dansiegel - Thanks so much for the sample code, and for explaining your approach! With over 2000 tests, and a large number of them verifying navigation calls under various circumstances, I am concerned about the amount of time it would take to restructure them all to implement this pattern. @brianlagunas - You mentioned that:
Are there any examples of this you could point me to? I've been experimenting trying to implement your idea over last night and this morning, and I'm still coming up with failure after failure. The class I created is called "FakePageNavigationService" (see below), and I've overridden the GoBackInternal and NavigateInternal methods. I also made a virtual method to match the signature of the NavigateAsync. That got rid of the errors about attempting to mock an extension method, and instead produces an error saying that NavigateAsync is never called, though NavigateInternal is. I'm not quite sure if I'm on the right path, or driving into a brick wall. Any tips would be greatly appreciated... We do TDD, so we have hundreds of tests of navigation calls. public class FakePageNavigationService : PageNavigationService
{
public FakePageNavigationService(IContainerExtension container,
IApplicationProvider applicationProvider,
IPageBehaviorFactory pageBehaviorFactory,
ILoggerFacade logger) : base(container, applicationProvider, pageBehaviorFactory, logger)
{ }
// MOQ requires a parameterless constructor:
public FakePageNavigationService() : base(null, null, null, null) { }
protected override Task<INavigationResult> GoBackInternal(INavigationParameters parameters, bool? useModalNavigation, bool animated)
{
return base.GoBackInternal(parameters, useModalNavigation, animated);
}
protected override Task<INavigationResult> NavigateInternal(string name, INavigationParameters parameters, bool? useModalNavigation, bool animated)
{
return base.NavigateInternal(name, parameters, useModalNavigation, animated);
}
// Attempt at 'replacing' the NavigateAsync extension method, though I suspect this is not the right thing to do.
public virtual Task<INavigationResult> NavigateAsync(string name, INavigationParameters parameters, bool? useModalNavigation, bool animated)
{
return null;
}
} |
Ok... We finally came up with a way to workaround the testing issue. I wanted to share with all in case this helps you out. There is probably a better way to do this, but I haven't found that particular solution yet. Step 1: Create public static Funcs to handle navigation. We used a simple static class we call Utilities. So far, there are 3 that we've made: public static Func<INavigationService, string, INavigationParameters, bool?, bool, Task<INavigationResult>> TestableNavigateAsync =
(navSvc, pageName, navParams, isModal, isAnimated) => navSvc.NavigateAsync(pageName, navParams, isModal, isAnimated);
public static Func<INavigationService, Task<INavigationResult>> TestableGoBackAsync =
(navSvc) => navSvc.GoBackAsync();
public static Func<INavigationService, INavigationParameters, bool?, bool, Task<INavigationResult>> TestableGoBackAsyncWithParams =
(navSvc, navParams, isModal, isAnimated) => navSvc.GoBackAsync(navParams, isModal, isAnimated); Step 2: Change your calls to the navigation service to use your new funcs. For example: private void OnNavigateToMyAwesomePageModal(Dictionary<string, List<MyCoolClass>> veryImportantDict)
{
var navigationParameters = new NavigationParameters();
navigationParameters.Add("keyToThatInfo", "very important info!");
//_navigationService.NavigateAsync("NameOfPageToNavigateTo", navigationParameters, true, true);
Utilities.TestableNavigateAsync(_navigationService, "NameOfPageToNavigateTo", navigationParameters, true, true);
} Step 3: Test to your heart's content! Maybe kinda like this: [Test]
public void TestNavigateToAwesomePageNavigates()
{
int numberOfCalls = 0;
var actualPageName = string.Empty;
INavigationParameters actualNavParams = null;
Utilities.TestableNavigateAsync = (navSvc, pageName, navParams, isModal, isAnimated) => {
++numberOfCalls;
actualPageName = pageName;
actualNavParams = navParams;
return Task.FromResult<INavigationResult>(null);
};
someCoolPageViewModel.NavigateToAwesomePage();
Assert.AreEqual(1, numberOfCalls);
Assert.AreEqual("expectedPageName", actualPageName);
Assert.AreEqual("very important info!", actualNavParams[keyToThatInfo]);
} |
Hello, just updated my project to the latest Prism version and all my unit tests are now failing with this error:
I guess this is because I am using AutoFixture to create my SUT (ViewModels) and since the Test Projects are .NET 4.6 types those interfaces are not there anymore Update: I also tried by mocking the PageNavigationService, that does not work anymore, on compilation saying something like:
when trying to manually instantiate the SUT view model any ideas on how to make this work ? basically all my viewmodels are now untestable because they require that INavigationService in the constructor |
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
I recently upgraded to the package in the CI feed to solve issue number #1311, thanks for the quick reply on that. Since I have upgraded to the CI package my unit tests have broken because I cannot Mock INavigationService.
I had previously created a custom mock object which implemented INavigationService but that broke because I cannot implement INavigateInternal due to its protection level.
I tried Moq (4.7.125) and passed a Mock but that failed with a System.InvalidCastException : Unable to cast object of type 'Castle.Proxies.ObjectProxy' to type.
How would you recommend Mocking the Navigation Service?
The text was updated successfully, but these errors were encountered: