Doug Mitchell
Doug Mitchell

Reputation: 192

Why are my WCF Async with Rx unit tests unstable?

I am using Rx and RxUI in a MVVM project and have a view model that queries its data from a WCF service asynchronously. In the unit tests I create a mock object that returns a Task with the expected value.

Here's a general idea of what my view model looks like

public class ViewModel : ReactiveObject
{
    private IContext _context;

    public ViewModel(IContext context)
    {
        _context = context;

        Observable.FromAsync(() => _context.WcfServiceCall())
              .Subscribe(result => {
                   Children.AddRange(results.Select(r => new ChildViewModel(r).ToList()));
               });
     }

     public ObservableCollection<ChildViewModel> { get; private set;}
}

My unit test looks like this

[TestFixture]
public class ViewModelTest : AssertionHelper
{
    [Test]
    public void ShouldSetChildren()
    {
        var c = new Mock<IContext>();
        c.Setup(q => q.WcfServiceCall())
            .Returns(Task.Run(() => new []{ 1,2,3,4,5,6 })):

        var vm = new ViewModel(c.Object);
        var p = vm.Children.First(); // this call sometimes fails

        ... 
     }
}

The issue I'm having is that I have over 400 tests that do this sort of thing and they all work most of the time but I randomly get failed tests, one or two at a time, that report the sequence has no values. This is unpredictable and random. I can run the tests again after a failure and all succeed. I have added the TestScheduler as described here but the problems persist.

Is there a better way to test methods that make asynchronous method calls?

Edit from Paul Bett's input: I see FromAsync does not take an IScheduler parameter but I do have SubscribeOn and ObserveOn available.

Alternatively, I could call the WCF async method directly and convert the returned Task to an observable. I'm not sure I understand when it is more appropriate to use Observable.FromAsync versus not using it.

Upvotes: 0

Views: 239

Answers (2)

Doug Mitchell
Doug Mitchell

Reputation: 192

I realize that my unit tests were incorrect in that they were touching too many moving parts. I have tests that verify that the mocked web service calls are being made when expected and I really shouldn't include that complexity in tests that aren't focused on that test point.

I'm calling the wcf service when navigating to the view model. Here's a better representation of my view model with minor changes regarding specifying an IScheduler:

public class ViewModel : ReactiveObject, IRoutableViewModel
{
    private IContext _context;
    public ViewModel(IContext context)
    {
        _context = context;
        Weeks = new ReactiveCollection<WeekViewModel>();

        this.WhenNavigatedTo(() =>
        {
            _context.Service.GetWeeksAsync()
                .ToObservable()
                .ObserveOn(RxApp.DeferredScheduler)
                .Subscribe(result =>
                {
                    Weeks.AddRange(result.Select(w => WeekViewModel(w)));
                });
        });

        // ...
    }

    public ReactiveCollection<WeekViewModel> Weeks { get; private set; }
}

Instead of setting up the context to populate the Weeks collection then using the router to navigate to the ViewModel I am now adding objects to the Weeks collection in the unit tests and skipping navigation and the asynchronous web service calls. This has reduced the tests a bit, eliminated the instability as far as I can tell, and reduced the execution time of test suite.

So my unit tests look something like this

[TestFixture]
public class ViewModelTest : AssertionHelper
{
    [Test]
    public void ShouldSetChildren()
    {
        var c = new Mock<IContext>();
        var vm = new ViewModel(c.Object);
        vm.Children.AddRange((new []{1,2,3,4,5,6}).Select(i => new ChildViewModel(i)));

        var p = vm.Children.First(); // this is valid now - no timing issues

        ... 
    }
}

The code behaves correctly in the app but was problematic in the test runner so I believe that this solves my immediate issues.

Upvotes: 0

Ana Betts
Ana Betts

Reputation: 74652

Does Observable.FromAsync take an IScheduler parameter? Is it your Test Scheduler?

Upvotes: 1

Related Questions