Testing ReactiveUI Commands is almost screaming Tell don't Ask at you.
I suppose may want to set a state on completion of the method invoked when executing the Command. e.g. DoExitViewModel()
, in the example. But only do this when the view needs to know a result. In this example you would've Disposed() any ViewModel dependencies; so the View can now close itself.
Do not set a property in the ViewModel just so that you test whether production code successfully completed. Use Tell Don't Ask instead.
Commands can rarely send data from the View to ViewModel. When a ViewModel needs to know under what state a Command was invokde, one would normally set a property before invoking a Command. In contrast, when the view needs to know a result, I'll always try to return that result to the view from the method executing in the ViewModel.
public partial class MyView : Window, IViewFor<MyViewModel> { public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel", typeof(MyViewModel) , typeof(MyView)); public MyViewModel ViewModel { get { return (MyViewModel)this.GetValue(ViewModelProperty); } set { this.SetValue(ViewModelProperty, value); } } object IViewFor.ViewModel { get { return this.ViewModel; } set { this.ViewModel = (MyViewModel)value; } } public partial class MyView() { InitializeComponent(); this.WhenActivated(disposables => this.BindControls(disposables)); } private void BindControls(CompositeDisposable disposableRegistration) { this.WhenAnyValue(v => v.ViewModel) .BindTo(this, v => v.DataContext) .AddTo(disposableRegistration); this.BindCommand(this.ViewModel, vm => vm.ExitView, v => v.btnExit) .AddTo(disposableRegistration); this.ViewModel.ExitView.Subscribe(success => { if (success) this.Close(); }); this.WhenAnyObservable(v => v.ViewModel.BarcodeScanned) .Subscribe(authenticationType => this.ShowAuthentication(authenticationType)); } } public class MyViewModel { public ReactiveCommand<Unit, bool> ExitView { get; private set; } // Output to view public ReactiveCommand<Unit, CRUDResult> AddTemplate { get; private set; } // Output public ReactiveCommand<string, AuthenticationTypes?> BarcodeScanned { get; } // Input from view + Output public ReactiveCommand<Unit, Unit> Boring { get; } // No input/output from/to view public IObservable<bool> CanAddTemplate { get; private set; } public MyViewModel() { this.CanAddTemplate = (this).WhenAnyValue(vm => vm.PageState, (EditState status) => status == EditState.Browse); this.AddTemplate = ReactiveCommand.Create(() => this.DoAddTemplate(), this.CanAddTemplate); this.BarcodeScanned = ReactiveCommand.Create<string, AuthenticationTypes?>(barcode => this.DoBarcodeScanned(barcode)); this.ExitView = ReactiveCommand.CreateFromTask(async () => await Task.Run(() => this.DoExitViewModel())); } public bool DoExitViewModel() { // Dispose the ViewModel return true; } private CRUDResult DoAddTemplate() { // Do whatever you do this.PageState = EditState.Add; return new CRUDResult(null); } private AuthenticationTypes? DoBarcodeScanned(string barcode) { return null; } } public class MyViewModelTests { [Test] public void Add_Button_Test() { // Arrange var vm = new MyViewModel(); vm.Setup(0); EditState expectedStatus = EditState.Add; CRUDResult resultPage = new CRUDResult(null); // Act vm.AddTemplate.Subscribe(tf => resultPage = tf); vm.AddTemplate.Execute().Subscribe(); // Assert Assert.AreEqual(expectedStatus, vm.PageState); Assert.AreEqual(null, resultPage.Message); } }
No comments:
Post a Comment