ReactiveUI: Testing observable properties from unit tests
Asked Answered
F

1

0

There is this example on the official RX blog:

var scheduler = new TestScheduler();

var xs = scheduler.CreateColdObservable(
    OnNext(10, 42),
    OnCompleted<int>(20)
);

var res = scheduler.Start(() => xs);

res.Messages.AssertEqual(
    OnNext(210, 42),            // Subscribed + 10
    OnCompleted<int>(220)       // Subscribed + 20
);

xs.Subscriptions.AssertEqual(
    Subscribe(200, 1000)        // [Subscribed, Disposed]
);

I'd like to do something like this with reactiveui. I mean instead of the scheduler.CreateColdObservable(...) use the streams from actual property change notification. The problem is that I tried vm.ObservableForProperty and vm.Changed but they worked inconsistently (not all property change created an event or the value was null)

Here is the code of my VM:

internal class ProductFileEditorVM : ReactiveObject
{
    private readonly List<string> _preloadedList;

    private bool _OnlyContainingProduct;
    public bool OnlyContainingProduct
    {
        get { return _OnlyContainingProduct; }
        set
        {
            this.RaiseAndSetIfChanged(x => x.OnlyContainingProduct, value);
        }
    }

    private ObservableAsPropertyHelper<IEnumerable<string>> _RepoList;
    public IEnumerable<string> RepoList
    {
        get{return _RepoList.Value;}
    }

    public ProductFileEditorVM(RepositoryManager repositoryManager)
    {

        //Set defaults
        OnlyContainingProduct = true;

        //Preload
        _preloadedList = repositoryManager.GetList();

        var list = this.WhenAny(x => x.OnlyContainingProduct,
                     ocp =>
                     ocp.Value
                         ? _preloadedRepoList.Where(repo => repo.ToLower().Contains("product"))
                         : _preloadedRepoList);
        list.ToProperty(this, x => x.RepoList);
    }
}

Ideally I'd like to use Observable.CombineLatest on the two property and creating a tuple and comparing this tuple in the assert expression like in the first example.

The good result would be:

  1. [OnlyContainingProduct==true;RepoList= the filtered one]
  2. !change OnlyContainingProduct to false
  3. [OnlyContainingProduct==false;RepoList= the whole list]

*Or is this the wrong way to approach it? The only example I saw about this uses actual time measures like milliseconds but I don't see how they are useful except in case of Throttle and similar methods. *

Firecracker answered 23/1, 2013 at 18:15 Comment(0)
S
4

So, because you're not doing tests that are related to time, only tests that are based on order (i.e. "I did This, then I did This, then it should be That), it's actually far simpler to just write a normal unit test. TestScheduler is a big hammer :)

So, you could do something like:

var fixture = new ProductFileEditorVM();

bool repoListChanged = false;
fixture.WhenAny(x => x.RepoList, x => x.Value)
    .Subscribe(_ => repoListChanged = true);

fixture.OnlyContainingProduct = true;
Assert.True(repoListChanged);

When to use TestScheduler

However, if loading RepoList was asynchronous and could take some time, and you wanted to represent a "Loading" state, a TestScheduler would be good for that - you'd click the checkbox at say +20ms, AdvanceTo(200ms), check to see if you're in Loading state, AdvanceTo(10min), then see that the list is updated and the state isn't Loading

Sleave answered 23/1, 2013 at 21:9 Comment(8)
OK, I'm closer to understand but still confused. How do I know that 200ms in the future is still the loading state? What if it loads super fast for some reason? The 10 minutes (or whatever) I understand because I can set a timeout to 10 mins.Firecracker
Ah, that's the part I left out - you mock the loading method of RepoList to be a CreateColdObservable that returns a single item at +200ms and completes. None of the times actually matter since TestScheduler runs them all instantly, it's just useful for readability and understanding the testSleave
OK, this way I can accept the answer. Just theoretically what if someone creates a ui with multiple controls that have async behavior and are interdependent. That is some super-complicated state machine. Do you think there is an ultimate pattern to test arbitrarily complex rx view model? Or the sample above (just more complicated) is always enough regardless the complexity and virtual time is only needed when there is some sort of real time?Firecracker
Unless you're testing something involving time (i.e. Timeout / Delay / Interval / Window), you never need TestScheduler, especially if you always make sure that every async method goes through RxApp.TaskpoolScheduler or RxApp.DeferredSchedulerSleave
Thank you! And is there any special consideration about testing stuff that uses RxApp.TaskpoolScheduler or RxApp.DeferredScheduler?Firecracker
In the test runner, both TaskpoolScheduler and DeferredScheduler are set to run synchronously, so if you have code that really does need > 1 thread (i.e. if you're taking locks or using First() / Wait(), which you shouldn't be), you will have to override it.Sleave
Thank you. BTW I think you are right about the part that I shouldn't be because that would break the messaging paradigm (at least I imagine it like messages). The other way all messages can be serialized like the STA aka. EventLoopSceduler works. I learn fast :)Firecracker
"In the test runner, both TaskpoolScheduler and DeferredScheduler are set to run synchronously" @Paul Betts: I just noticed that RxApp.TaskpoolScheduler is set to TaskpoolScheduler in the unit tests (using wpf)? Is this a bug?Firecracker

© 2022 - 2024 — McMap. All rights reserved.