ObservableAsPropertyHelper - have to access Value to get it to subscribe?
Asked Answered
I

1

1

I recently updated my ReactiveUI nugget packages to the latest pre-release versions (v5.99.4-beta). It has been a while since I did the update, but many of my tests on ViewModel's failed.

The debugger seemed to indicate that my normal sequences of Rx weren't getting subscribed to. For example:

  _executeSearch = ReactiveCommand.Create();
  var paper = _executeSearch
              .Where(x => x is string)
              .SelectMany(x => _searchParser.GetPaperFinders(x as string))
              .SelectMany(x => x.FindPaper());
  paper
       .Select(x => x.Item1.Title)
       .ToProperty(this, x => x.Title, out _TitleOAPH, "");

And my tests code looked something like this (cut for brevity):

  INavService obj = Mock...
  ISearchStringParser parser = ...

  var vm = new AddCDSPaperViewModel(obj, parser);
  vm.CDSLookupString = "1234"; // trigger the "search"
  ... (test scheduler is advanced to wait for search results to show up
  Assert.AreEqual("title", vm.Title, "searched for title"); // vm.Title is unset, though it should be!

I looked at the reactiveui source code on github and discovered the following for the "value" property on the ObservableAsPropertyHelper object:

    public T Value {
        get { 
            _inner = _inner ?? _source.Connect();
            return _lastValue; 
        }
    }

The key line there is "_source.Connect()" - in short, the sequence isn't subscribed to until someone access Value. In my tests, if I put a "vm.Title" before I run any rx sequences, then everything works just fine.

This is surprising behavior (at least to me), in the sense that the ObservableAsPropertyHelper.Value property has to be accessed before it will capture any values. Is this expected behavior? Was it done for efficiency (e.g. lazy instantiation) reasons? If so, what is the proper way around this in tests?

Or am I very mistaken in how this works? :-)

Intensifier answered 16/3, 2014 at 4:4 Comment(0)
P
1

Is this expected behavior?

Yep, this is new for ReactiveUI 6.0.

Was it done for efficiency (e.g. lazy instantiation) reasons?

Correct - it actually can end up saving a lot of memory and work! (15-20% memory as I recall in GitHub for Windows on my early tests)

This is an interesting scenario though - I suspect that in the unit test runner, we should be subscribing on construction, similar to what RxUI 5.x did.

Edit: I would write it this way:

_executeSearch = ReactiveCommand.Create(_ => Observable.Return(x)
    .Where(x => x is string)
    .SelectMany(x => _searchParser.GetPaperFinders(x as string))
    .SelectMany(x => x.FindPaper()));

_executeSearch
   .Select(x => x.Item1.Title)
   .ToProperty(this, x => x.Title, out _TitleOAPH, "");

Now, you can write your test way easier, without TestScheduler:

var vm = new AddCDSPaperViewModel(obj, parser);
var result = await vm.ExecuteSearch.ExecuteAsync("Some String");
Assert.AreEqual("title", result.Title);
Pat answered 16/3, 2014 at 7:26 Comment(5)
How do I get the unit test runner to subscribe on construction? I'm not sure how I even know what all the properties are that have the ObservableAsPropertyHelper. Or do you mean create a global flag, and then have the ObservableAsProertyHelper subscribe when it is set? Have to be careful if it makes unit tests behave differently from real world... Perhaps my unit tests aren't structured correctly?Intensifier
Nice, thanks. I'll look into this. Actually, however, there are waits and timeouts and de-bounce logic in there that I need to test, so I still need the test scheduler - I just dropped all that from the question above. However, I still like your organization better.Intensifier
Ah in that case use TestScheduler, but the ExecuteAsync still is easierPat
Definitely, I just tried it. I like putting more logic in there, what stopped me was I thought the subscription to _executeSearch would return the parameter passed to the "Execute" method on the ReactiveCommand, not the result of the async call. I must have been confused!Intensifier
No worries, the new Command stuff is brand shining new, no docs about it yet other than the PRPat

© 2022 - 2024 — McMap. All rights reserved.