如何使用 NUnit 测试 MvvmCross MvxCommand<int>

How to test MvvmCross MvxCommand<int> with NUnit

当我的命令需要整数类型的 CommandParameter 时,如何为 MvvmCross MvxCommand 编写 NUnit 测试。

testListViewModel.EditCommand.Execute(null);

这不是一个选项,因为我有这个 ViewModel。如果传递了参数,则 CanExecute 会继续执行。

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

我在我的测试项目中使用 NUnit + Moq + Cirrious.MvvmCross.Test.Core 引用并具有此结构。

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

当我 运行 测试时,它会使用此调用堆栈抛出 MvxIoCResolveException。我尝试调试它,但我的代码在 MvxNavigatingObject.ShowViewModel() 上确实失败了,其中参数是一个对象并尝试调用 parameterValuesObject.ToSimplePropertyDictionary().
在静态函数 ToSimplePropertyDictionary(this object input) propertyInfos 枚举数为空,因此无法调用 foreach(propertyInfos 中的 var propertyInfo)

我从 运行ning 测试中得到的调用堆栈是:

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

如有任何帮助,我们将不胜感激!

当您使用参数调用 ShowViewModel 时,有几种不同的方法可以做到这一点。这决定了 MvvmCross 如何调用您的 Init 方法。

  • 单个简单类型参数
  • 具有简单类型化属性的单个类型化参数对象
  • 作为带有 IMvxBundle 参数的 InitFromBundle() - 最后一种风格始终通过 IMvxViewModel 接口支持。

您正在使用匿名类型调用 ShowViewModel,但没有 属性 名称。这些 属性 名称必须与您的 Init 方法中的参数名称匹配。

所以不是这个:

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

这样做:

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

或者这样:

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

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

   // for navigation parsing
   Ioc.RegisterSingleton<IMvxStringToTypeParser>(new MvxStringToTypeParser());

https://github.com/MvvmCross/MvvmCross/wiki/Testing