Search code examples
c#unit-testingasynchronousreactiveui

How to test ReactiveUI calling async method with SelectMany


I'm trying to write an integration test for a Viewmodel that calls an async method on my model(Foo). However I can't get the timing/threading/async processing to work.

I've reduced to problem to the code below. For now this test fails because the actual value of myFoo.Value is 0 instead of 123.

using System.Reactive;
using Microsoft.Reactive.Testing;
using ReactiveUI.Testing;
using ReactiveUI.Fody.Helpers;
using ReactiveUI;
using System.Reactive.Linq;
using System.Reactive.Concurrency;

namespace Cooler.Test;

public class Foo
{
    public int Value { get; private set; }

    public Foo() => Value = 42;

    public async Task<Unit> SetValueAsync(int value)
    {
        await RxApp.TaskpoolScheduler.Sleep(TimeSpan.FromMilliseconds(10));
        Value = value;
        return Unit.Default;
    }
}

public class ViewModel : ReactiveObject
{
    public ViewModel(Foo foo)
    {
        this.WhenAnyValue(x => x.Setpoint)
            //.Skip(1) // Skip the initial value
            .ObserveOn(RxApp.MainThreadScheduler)
            .SelectMany(foo.SetValueAsync)
            .Subscribe();
    }

    [Reactive]
    public int Setpoint { get; set; }
}


public class Test
{
    [Fact]
    public void ShouldCallAsyncMethodOnSettingReactiveSetpoint()
    {
        new TestScheduler().With(scheduler =>
        {
            //set
            var myFoo = new Foo();
            var myVm = new ViewModel(myFoo);

            //act
            scheduler.AdvanceBy(1); //process the initial value if not skipped
            scheduler.AdvanceByMs(20); //async processing

            myVm.Setpoint = 123;
            scheduler.AdvanceBy(2); //process reactive
            scheduler.AdvanceByMs(20); //I expect it to process setpoint setting

            //assert
            Assert.Equal(123, myFoo.Value);
        });
    }
}

I expected the value to be 123. But foo.SetValueAsync is not called with 123. Only the initial call with value 0. If I use the skip(1) line the value is 42, which is logical seeing the previous result.

Why does this test fail?


Solution

  • I was mistaken in thinking the method called by SelectMany would run on the RxApp.MainThreadScheduler as well. It doesn't. So the Assert is finished before SetValueAsync is called.