2

I'm trying to combine ReactiveCommand with ServiceStack asynchronous API.

The x => _reactiveList.AddRange(x) is never called and the test res is null. I don't now how to convert ServiceStack's Task<TResponse> GetAsync<TResponse>(IReturn<TResponse> requestDto) result into reactive IObservable<T>.

    public ReactiveCommand<IList<string>> ServiceReadCommand { get; protected set; }

    public ReactiveList<string> ReactiveList
    {
        get { return _reactiveList; }
        set { _reactiveList = this.RaiseAndSetIfChanged(ref _reactiveList, value); }
    }

    private ReactiveList<string> _reactiveList = new ReactiveList<string>();

    public TestViewModel(IScreen screen = null)
    {
        HostScreen = screen;

        ServiceReadCommand = ReactiveCommand.CreateAsyncTask(x => ServiceCommandTask(), RxApp.TaskpoolScheduler);
        ServiceReadCommand.ThrownExceptions.Subscribe(x => Console.WriteLine((object)x));
        ServiceReadCommand.Subscribe(x => _reactiveList.AddRange(x));
    }

    private async Task<IList<string>> ServiceCommandTask()
    {
        this.Log().Info("Service command task");

        var baseUri = "http://localhost:9010";
        var client = new JsonServiceClient(baseUri);

        // Works
        return await Observable.Return(new List<string> { "" });

        // Don't
        return await client.GetAsync(new TestRequest());
    }

And test method:

    [TestMethod]
    public void TestMethod1()
    {
        IList<string> res = null;
        new TestScheduler().With(sched =>
        {
            var viewModel = new TestViewModel();

            viewModel.ServiceReadCommand.CanExecute(null).Should().BeTrue();
            viewModel.ServiceReadCommand.ExecuteAsync(null).Subscribe(x => res = x);

            sched.AdvanceByMs(1000);

            return viewModel.ReactiveList;
        });

        res.Should().NotBeEmpty();
    }

I have added console application with all code. Change ServiceCommandTask to IObservable<T> didn't helped. Adding Thread.Sleep() between

viewModel.ServiceReadCommand.ExecuteAsync(null).Subscribe(x => res = x);
//Thread.Sleep(1000);
sched.AdvanceByMs(1000);

resolves the issue but this is not an option.

Tomasito
  • 1,864
  • 1
  • 20
  • 43

2 Answers2

1

OK, there are a couple things that could be wrong. Everything looks like it should run, but you never know.

I do know that there are usually some weird things in ReactiveUI when dealing directly with Tasks and the TestScheduler though, so there are a couple of things that you could try on that front.

  1. You could use the ToObservable() extension method to convert the Task<TResponse> into an IObservable<TResponse>.

    using System.Reactive.Linq;
    // ...
    return await (client.GetAsync(new TestRequest()).ToObservable());
    
  2. The other idea has to do with forcing your ServiceCommandTask() method to return an IObservable and not use async/await. To do that you would have to change how you create your ReactiveCommand to use ReactiveCommand.CreateAsyncObservable and you would just use ToObservable() in ServiceCommandTask() on the Task that is returned from the ServiceStack call.

The reason why converting the Task into an IObservable might work is because the TestScheduler relies on virtual time to wait for things to happen. The TestSchedluer cannot interact properly with the Task and therefore when it "skips" ahead 1 second no time has actually passed in the real world. If the ServiceStack call does not return instantly then I would expect that to be the case and that problem may or may not be fixable by the above solutions.

AtinSkrita
  • 1,373
  • 12
  • 13
  • I think it's timing problem but I don't know how to resolve it. Sample code on GitHub – Tomasito Mar 05 '15 at 14:00
  • @Tomasito Yeah, I think your biggest problem is that you are [actually writing an integration test, not a unit test](http://stackoverflow.com/questions/10752/what-is-the-difference-between-integration-and-unit-tests). It is an integration test because it uses your ServiceStack dependency instead of mocking it. Because you are trying to perform an operation that takes *real* time, `AdvanceByMs(1000)` will not help you out at all. You instead have to wait for *real* time, which means that you have to use `Thread.Sleep(1000)`. – AtinSkrita Mar 05 '15 at 16:49
  • `Thread.Sleep(1000)` works only in `Main(string[] args)`, when in `TestMethod1` it gives me `ServiceStack.WebServiceException: Method Not Allowed` exception. Last executed method is `System.Reactive.ScheduledObserver`1.Run(Object state, Action`1 recurse)`. Is my all architecture wrong? I mean calling service from ReactiveCommand? – Tomasito Mar 05 '15 at 17:09
  • @Tomasito I would say that calling ServiceStack from your reactive command **when you are unit testing** is bad because then you are trying to test ServiceStack as well as your ReactiveCommand at the same time. I would move your `GetAsync` method into an interface that you could mock (i.e. replace with a dummy version) inside your unit test so that the only functionality you are testing is your ReactiveCommand functionality. If you need to test whether ServiceStack works then you should make a different unit test for that. – AtinSkrita Mar 05 '15 at 17:24
  • I need at least one test to make sure they are working together. True? – Tomasito Mar 05 '15 at 18:58
  • @Tomasito Actually, no. If you have a test to make sure that your ReactiveCommand is working as expected and you have a test to make sure that ServiceStack is working as expected then you know for a fact that they will work together. That is the logic that [Unit Testing](http://en.wikipedia.org/wiki/Unit_testing) is based upon. [Check out this answer to see what I mean](http://stackoverflow.com/a/7876055/1832856) – AtinSkrita Mar 05 '15 at 19:16
0

You're doing everything right on the ReactiveUI end, something's going on with ServiceStack.

Ana Betts
  • 73,868
  • 16
  • 141
  • 209