Skip to content
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

Closed
Bjselous opened this issue Dec 22, 2017 · 14 comments
Closed

Unable to Mock INavigationService #1312

Bjselous opened this issue Dec 22, 2017 · 14 comments

Comments

@Bjselous
Copy link

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?

@Bjselous
Copy link
Author

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'.

@brianlagunas
Copy link
Member

You should inherit from the PageNavigationService and override the internal methods. Let me know if this still is a blocker for you.

@MaikuB
Copy link

MaikuB commented Feb 1, 2018

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.

@dansiegel
Copy link
Member

I would tend to imagine that if you update your Navigation calls to no longer specify useModalNavigation or animated then you wouldn't be referencing INavigationServiceExtensions and would not have a problem with Moq....

@thomashoef
Copy link

@dansiegel yeah, your answer is exactly right

@VBPrakash
Copy link

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.

Hi MaikuB, Can you please post some sample code how you managed to mock INavigationService ?

@jbachelor
Copy link

@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?

@MaikuB
Copy link

MaikuB commented Nov 1, 2018

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

@brianlagunas
Copy link
Member

I think we can fix this simply by making INavigateInternal public

https://github.com/PrismLibrary/Prism/blob/master/Source/Xamarin/Prism.Forms/Navigation/INavigateInternal.cs

@dansiegel
Copy link
Member

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();
    }
}

@jbachelor
Copy link

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:

You should inherit from the PageNavigationService and override the internal methods.

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;
        }
    }

@jbachelor
Copy link

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]);
}

@WarBorg
Copy link

WarBorg commented Jun 23, 2019

Hello,

just updated my project to the latest Prism version and all my unit tests are now failing with this error:

System.TypeLoadException : Could not resolve type with token 01000011 from typeref (expected class 'Prism.Navigation.INavigationAware' in assembly 'Prism, Version=7.1.0.431, Culture=neutral, PublicKeyToken=40ee6c3a2184dc59')

  at (wrapper managed-to-native) System.Reflection.MonoMethod.InternalInvoke(System.Reflection.MonoMethod,object,object[],System.Exception&)
  at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x0003b] in /Users/builder/jenkins/workspace/build-package-osx-mono/2018-08/external/bockbuild/builds/mono-x64/mcs/class/corlib/System.Reflection/MonoMethod.cs:305

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:

Error CS7069: Reference to type 'INavigationService' claims it is defined in 'Prism', but it could not be found (CS7069)

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

@lock
Copy link

lock bot commented Jan 28, 2020

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.

@lock lock bot locked as resolved and limited conversation to collaborators Jan 28, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants