Search code examples
parametersnunitmvvmcrossexecuteicommand

How to test MvvmCross MvxCommand<int> with NUnit


How do I write a NUnit test for MvvmCross MvxCommand when my command needs CommandParameter of type integer.

testListViewModel.EditCommand.Execute(null);

This one is not an option since i have this ViewModel. CanExecute proceeds if parameter is passed.

public class TestListViewModel : MvxViewModel
{
    private readonly ITestService _testService;
    private readonly IDialogService _dialogService;
    public TestListViewModel(ITestService testService, IDialogService dialogService)
    {
        _testService = testService;
        _dialogService = dialogService;
    }   

    public MvxCommand<int> EditCommand { get { return new MvxCommand<int>(Edit, id => id > 0); } }      

    private void Edit(int asTestID) { ShowViewModel<TestViewModel>(new { asTestID }); }
}

I use NUnit + Moq + Cirrious.MvvmCross.Test.Core references in my test project and have this structure.

public class MockDispatcher : MvxMainThreadDispatcher, IMvxViewDispatcher
{
    public readonly List<MvxViewModelRequest> Requests = new List<MvxViewModelRequest>();
    public readonly List<MvxPresentationHint> Hints = new List<MvxPresentationHint>();
    public bool RequestMainThreadAction(Action action)
    {
        action();
        return true;
    }

    public bool ShowViewModel(MvxViewModelRequest request)
    {
        Requests.Add(request);
        return true;
    }

    public bool ChangePresentation(MvxPresentationHint hint)
    {
        Hints.Add(hint);
        return true;
    }
}

[TestFixture]
class TestListViewModelTest : MvxIoCSupportingTest
{
    [Test]
    public void TestListViewModel_OnEdit_ShowTestViewModel()
    {
        //Arrange
        base.ClearAll();

        var mockDispatcher = new MockDispatcher();
        Ioc.RegisterSingleton<IMvxViewDispatcher>(mockDispatcher);
        Ioc.RegisterSingleton<IMvxMainThreadDispatcher>(mockDispatcher);

        var mockDialogService = new Mock<IDialogService>();
        var mockTestService = new Mock<ITestService>();

        var testListViewModel = new TestListViewModel(mockTestService.Object, mockDialogService.Object);

        //Act
        testListViewModel.EditCommand.Execute(2); //this line is failing

        //Assert
        Assert.AreEqual(1, mockDispatcher.Requests.Count);
        var request = mockDispatcher.Requests[0];
        Assert.AreEqual(typeof(TestViewModel), request.ViewModelType);
        Assert.AreEqual((2).ToString(), request.ParameterValues["asTestID"]);
    }
}

When i run the test it throws MvxIoCResolveException with this call stack. I tried to debug it and my code literally fails on MvxNavigatingObject.ShowViewModel() where the parameter is an object and tries to call parameterValuesObject.ToSimplePropertyDictionary().
In the static function ToSimplePropertyDictionary(this object input) propertyInfos enumerator is null, so it cant call foreach (var propertyInfo in propertyInfos).

The call stack that I get from running the test is:

Cirrious.CrossCore.Exceptions.MvxIoCResolveException : Failed to resolve type Cirrious.MvvmCross.Platform.IMvxStringToTypeParser
   at Cirrious.CrossCore.IoC.MvxSimpleIoCContainer.Resolve(Type t)
   at Cirrious.CrossCore.IoC.MvxSimpleIoCContainer.Resolve()
   at Cirrious.MvvmCross.MvxSingletonCache.get_Parser()
   at Cirrious.MvvmCross.Platform.MvxSimplePropertyDictionaryExtensionMethods.<ToSimplePropertyDictionary>b__7(PropertyInfo property)
   at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
   at Cirrious.MvvmCross.Platform.MvxSimplePropertyDictionaryExtensionMethods.ToSimplePropertyDictionary(Object input)
   at Cirrious.MvvmCross.ViewModels.MvxNavigatingObject.ShowViewModel(Object parameterValuesObject, IMvxBundle presentationBundle, MvxRequestedBy requestedBy)
   at App.Core.ViewModels.TestListViewModel.Edit(Int32 asTestID) in TestListViewModel.cs: line 37
   at App.Tests.Test.TestListViewModelTest.TestListViewModel_OnEdit_ShowTestViewModel() in TestListViewModelTest.cs: line 97

Any help would be appreciated!


Solution

  • When you call ShowViewModel with a parameter, there are a few different ways to do this. This determines how MvvmCross calls your Init method.

    • individual simply-Typed parameters
    • a single Typed parameter object with simply-Typed properties
    • as InitFromBundle() with an IMvxBundle parameter - this last flavor is always supported via the IMvxViewModel interface.

    You are calling ShowViewModel with an anonymous type, but without property names. These property names must match the parameter names in your Init method.

    So instead of this:

    private void Edit(int asTestID) { ShowViewModel<TestViewModel>(new { asTestID }); }
    

    Do this:

    // simply-Typed parameters
    private void Edit(int asTestID) { ShowViewModel<TestViewModel>(asTestID); }
    

    Or this:

    //  a single Typed parameter object with simply-Typed properties   
    private void Edit(int asTestID) { ShowViewModel<TestViewModel>(new { ID = asTestID }); }
    

    See https://github.com/MvvmCross/MvvmCross/wiki/View-Model-Lifecycle#2-init