Tuesday, August 3, 2010

Using Reactive Extensions (Rx) to Simplify Asynchronous Tests

Reactive Extensions (Rx) is a product from Microsoft Research that simplifies the management and composition of asynchronous events. If you read my earlier post on Asynchronous Workflows, you'll understand why the asynchronous programming model can sometimes lead to confusing and hard-to-maintain code.

There is nothing wrong with the model, but the fact that you must subscribe to an event or register a callback means your code can end up nested and scattered to the winds. Add to that the complexity of aggregating events (for example, firing multiple asynchronous events and then waiting for them all to complete before going on) and it's enough to require a visit to the pharmacy. OK, that was a bad lead-in because of course that's where you can get your Rx (prescription).

There is a lot to explore in the framework, but you can start by visiting the main site and downloading the extensions (they are available for .NET 3.5, .NET 4.0, Silverlight 3, Silverlight 4 and JavaScript). Probably the best and easiest way to learn using Rx is to follow the hands-on labs that you can download in PDF format from this blog post.

I'd like to give a gentle introduction by demonstrating how it solved a very specific need of mine. A very common pattern is what I'll call "synchronized lists" where one list triggers a change in the other. The classic example is selecting a country to get a list of states for that country. Selecting a country means we fetch or filter a new list of states, and if we're kind to the end user we'll also give them a default (either a state or perhaps a "select one" prompt). One test would be changing the country and validating that the list of states updated correctly. Another test, important for data-binding, is to ensure that setting the country raises both the country and the state property changed events. If someone were to come along and change the setter to the private property, the property change notification would fail to fire and we'd lose our data-binding.

To test is straightforward: set up the view model, register to the event, then listen and confirm we get the properties we are looking for. Implementing it is another story. The original syntax would involve registering a lambda that adds to a list, then waiting for a particular time (to make sure all events have fired) and then check the list. There is no "clean" way to do it without timers or a counter or making assumptions about the order the properties may come in.

The Reactive Extensions simplify this tremendously. Take a look at the following code:

public void TestChangeCurrentCountry_RaisesPropertyChanged()
{
    var properties = new List<string>();
    var expected = new[] {"CurrentCountry", "CurrentState"};

    // set it up
    _target.OnImportsSatisfied();

    var propertyChanged = (from evt 
      in Observable.FromEvent<PropertyChangedEventArgs>(_target, "PropertyChanged")
      select evt.EventArgs.PropertyName).Take(2);
                
    using (propertyChanged.Subscribe(
      properties.Add,
      ex => Assert.Fail(ex.Message),
      () =>
         {                                                 
            CollectionAssert.AreEquivalent(expected, properties,
            "Invalid properties were raised on the notify change event.");
            EnqueueTestComplete();
         }))
    {
        _target.CurrentCountry = _countries[1];
    }
}

What's going on here? I set up a list to grab properties as they are raised, and I initialize a list of expected properties. This makes the conditions of the test very clear. I'm using MEF, so what happens in unit tests is that I set up the view model with a bunch of mock objects to return my data: i.e. "if you ask for a country, I'll give you this" and so forth. I explicitly call "OnImportsSatisfied" to simulate what would happen if I were actually composing the view model with MEF (in this case, however, my test is composing it).

In this case, the view model will call the country service to populate the list, then the state service, and set defaults. All of these are fed based on the mock configurations.

Once we have the view model prepared, the Rx steps in. I set up an observable collection that is based on the property changed event. Notice we pass the expected EventArgs, the object to inspect and the event to listen for. The power of Rx is that it will asynchronously build the list for me as the event is raised, in this case returning the property names (because of my select statement). I am only expecting 2, so I can use the standard LINQ "Take(2)" for the list to stop after the second property changed event (otherwise, it would continue listening and building out the list ad infinitum).

The using statement provides an action as items are added (note I'm using a method group to specify that the string is passed to the add method of the properties list.) If an exception is thrown, I'll fail the test gracefully with an assert. The last parameter is the action to perform when complete, which is after both properties have been added. Here, I compare the two lists and let the Silverlight Unit Testing Framework know that the test is complete.

The entire statement wraps the functionality I'll use to trigger the behaviors - in this case, changing the country to set off the property change notification.

While this is a very simple example, I encourage you to download the hands on labs to see more complex examples that compose multiple asynchronous events together in a single collection. It's a powerful framework that deserves a close look!

Jeremy Likness

6 comments:

  1. I've always solved this problem (if I understand the problem correctly) using the AutoResetEvent class. Something like this (NOTE: I didn't try to compile this code, but I think it's close)...


    public void TestChangeCurrentCountry_RaisesPropertyChanged()
    {
    var countryEvent = new AutoResetEvent(false);
    var stateEvent = new AutoResetEvent(false);

    // set it up
    _target.OnImportsSatisfied();

    _target.PropertyChanged += (s, e) =>
    {
    if (e.PropertyName == "CurrentCountry")
    {
    countryEvent.Set();
    }
    else if (e.PropertyName == "CurentState")
    {
    stateEvent.Set();
    }
    else
    {
    Assert.Fail("Invalid property changed.");
    }
    };

    _target.CurrentCountry = _countries[1];

    Assert.IsTrue(countryEvent.WaitOne(1000), "Country change event not fired");
    Assert.IsTrue(stateEvent.WaitOne(1000), "State change event not fired");
    }


    This is still not beautiful code, but might be a slightly more readable then the original.

    ReplyDelete
  2. Sure thing. And it will work for unit tests - the beauty of Rx is that it can apply to similar scenarios in production, and the key is (a) it doesn't block the UI thread (the wait WILL block the UI thread, and in this example the only saving grace is that the events fire fast, if you were to simulate a longer event then the browser would lock) and (b) you can also easily dispatch your events back to the UI thread from the background.

    ReplyDelete
  3. Agreed. I would not use that for production code and the Rx framework does look pretty cool. Just thought the AutoResetEvent code looked a little cleaner in this particular case :)

    Oh, and thanks for the great blog. It's been one of my new favorites!

    ReplyDelete
  4. Appreciate the feedback, and the contributions. Comments have helped me learn and grow quite a bit through this blog! Thanks.

    ReplyDelete
  5. A collegue has written quite a bit about Rx, e.g. http://www.minddriven.de/index.php/technology/dot-net/rx-event-composition-multi-valued

    You may want to check this oput, he goes well beyond the initial simple examples.

    ReplyDelete
  6. I'd simply use
    properties = propertyChanged.ToEnumerable().ToList()
    in this case.
    I don't have experience in silverlight unit-tests so I don't know if the blocking nature of this call is a problem.

    ReplyDelete