Question

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? :-)

Was it helpful?

Solution

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);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top