2

I'm pretty green with rx/ReactiveUi and want to write a xunit test using TestScheduler to check if the throttle for retrieving search suggestions is working properly.

The idea is to use the TestScheudler for the timing, change the value for the search-term property and check if an async method is called. Unfortunately the method is not called at the expected position (see attached code, especially the unit test).

What am I missing? Is the way I'm trying to test this a good way to go?

My view Model:

public class MyViewModel : ReactiveObject
{
    public MyViewModel (IMyQueryHandler queryHandler)            
    {
        ...
        // Type suggestions
        this.SearchTerms = this.ObservableForProperty(x => x.SearchTerm)
            .Throttle(SuggestionThrottle).Value();

        this.SearchTerms.Subscribe(this.LoadSearchSuggestionsAsync);
        ...
    }

    internal async void LoadSearchSuggestionsAsync(string search)
    { 
        ... 
    this.SearchSuggestions = this.queryHandler.ExecuteQuery(...);
        ...
    }       

    public IList<SearchSuggestion> SearchSuggestions
    {
        get { return this.searchSuggestions; }
        set { this.RaiseAndSetIfChanged(ref this.searchSuggestions, value); }
    }

    ...
} 

My Unit Test (xunit):

...

public class TestFixture : ReactiveObject
{
    public string SearchTerms { get { return this._searchTermsBackingField.Value; } }
    public ObservableAsPropertyHelper<string> _searchTermsBackingField;
}

[Fact]
public void WillTryToLoadSearchSuggestionsAfterThrottleTime()
{
    new TestScheduler().With(
        sched =>
        {                    
            var fixture = new TestFixture();
            var queryClient = Substitute.For<IMyQueryHandler>();

            var caseSuggestions = new List<...> { ... }

            queryClient.ExecuteQuery<...>(...).ReturnsForAnyArgs(...);  // Nsubstitute

            var vm = new MyViewModel(queryClient);

            vm.SearchTerms.ToProperty(fixture, p => p.SearchTerms, out fixture._searchTermsBackingField);

            sched.Schedule(() => vm.SearchTerm = "Tes");
            sched.Schedule(MyViewModel.SuggestionThrottle, () => vm.SearchTerm = "Test");

            sched.AdvanceBy(MyViewModel.SuggestionThrottle.Ticks);
            sched.AdvanceBy(1);

            // why is the method MyViewModel.LoadSearchSuggestionsAsync not called here (in debug)???

            sched.AdvanceBy(1);
         }  // method gets called here...
 }
 ...    
road242
  • 2,492
  • 22
  • 30

1 Answers1

4

Throttle doesn't set the scheduler, write Throttle(timespan, RxApp.MainThreadScheduler) instead

Ana Betts
  • 73,868
  • 16
  • 141
  • 209
  • I'm probably missing something here - but I was just looking https://github.com/reactiveui/ReactiveUI/blob/master/ReactiveUI/RxApp.cs - according to the comments the `MainThreadScheduler` is the equivalent of the DispatcherScheduler - would you want a Throttle to run there? Maybe `RxApp.TaskpoolScheduler` followed by an `ObserveOn(RxApp.MainThreadScheduler)` (or whatever the idiomatic ReactiveUI is) would be advisable? – James World Dec 22 '14 at 17:46
  • 1
    No, the DispatcherScheduler is far more efficient for scheduling timers to than the TaskPoolScheduler - the former involves just adding an item to a list, and the latter involves creating Real Timers. Think about it - the Dispatcher runs all the time as long as the app is running - all it has to do is check the current time and compare it to a Priority Queue's top item – Ana Betts Dec 22 '14 at 21:42
  • Do you know what, I have no idea what I was thinking and you are of course completely right. In fact I'm a bit embarrassed. Very tempting to delete my comment and destroy the evidence, but that just wouldn't do! Maybe it's time to put down the keyboard for Christmas! Cheers Paul. – James World Dec 22 '14 at 23:59
  • Hmmm, looking around, it's surprising to see that there's not a lot of examples where a scheduler is specified for the `Throttle` in this sort of scenario, and when it is its often in the context of testability rather than efficiency. Certainly with WPF it will default to the platform default and not the dispatcher. Glad this question came up as I shall be on the alert for this now! – James World Dec 23 '14 at 00:48