Tuesday 12 March 2019

[ReactiveUI] Test command

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