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