如何使用 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());
当我的命令需要整数类型的 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());