Thursday, February 26, 2015

Using ImportMany For Lazy Initialisation With Reactive Extensions (RX) and WPF/Prism




Working on a WPF application, I had a requirement to allow a user to monitor the status of a number of connected services – the status of some services could fluctuate many times per second.


There were a couple of issues that needed to be considered:

  1. How would I get hold of these monitored ‘services’?  Could I just create new instances and pass them into my monitoring class?  Unfortunately not, as I didn’t know until runtime what these services were.
     
  2. How would I throttle the status updates to make them more presentable to the user without resulting in some sort of flashing Christmas tree effect?
      
  3. How would I unit test the monitoring behaviour?
     
  4. How would I display the status of specific items in an easy to understand manner?  Did I want to wait for a disconnect status and show that in, say, red? 



I came up with a solution that had the following features:

  • Use the Managed Extensibility Framework (MEF) to discover, at runtime, a collection of lazily loaded classes which implement and export a specific interface (my IConnectable in this case).
     
  • Use Reactive Extensions (RX) to throttle the frequency of updates in a more user friendly frequency - using the Sample() extension method.
     
  • Use WPF Bindings and a Converter to format the appearance of individual items in a DataGrid.
      
  • Extend INotifyPropertyChanged to include compiled properties for improved speed and change notification.
     
  • Use NUnit and Reactive Testing’s TestScheduler to be able to move time forward to confirm that frequently updated status updates are indeed throttled back.
     
  • Use PRISM’s MefBootstrapper to provide a basic Shell container (NB: this is by no means an exercise on how to design region specific Shell containers…I’ll save that for another day)


Before I go into details, the running screens look like this (clearly this is not an exercise in engaging screen design – I’m using the standard DataGrid):

Splash screen at start-up:



Status screen in action:


I’m going to use separate assemblies for the key areas of the solution:



PricingServices: collection of dummy “services” that implement my IConnectable interface.  These all perform the same task of notifying when a connection status changes, albeit at differing times.  This assembly is copied to an Extensions folder at compile time – the UI AppBootStrapper looks in that folder at runtime (see later section).

ServiceDashboard.Core: core interfaces and abstract classes shared amongst projects.

ServiceDashboard.Presentation: view model that react to changes in connection status (you could argue that these ought to be defined as Models).

ServiceDashboard.UI: WPF application that displays the results from the ConnectablesStatusViewModel.

ServiceDashboard.UnitTest: unit tests for the solution.

Task 1 – Define a Connectable Interface

Note: you’ll need to run “install-package Rx-PlatformServices” into Package Manager to include the RX PlatformService and related assemblies from NuGet.

In the Core project (ServiceDashboard.Core) I need to add the interface which each connectable service must implement and Export via MEF in order to be discovered (IConnectable):

using System;
namespace ServiceDashboard.Core
{
    public enum ConnectionStatus
    {
        Unknown,
        Connected,
        Disconnected,
        Error
    }

    public interface IConnectable
    {
        IObservable<ConnectionStatus> Status { get; }
        string Description { get; }
    }
}

When the app starts, MEF will discover all classes decorated with the Export attribute and the IConnectable type (more on that later).

The Status property is an observable so that we can listen changes in the connection status – it’s up to each concrete implementation to determine how and when the status actually changes (as you’d expect in a real system).

I’ve also added, NotifyPropertyChanged.cs, a standard implementation of INotifyPropertyChanged interface (using CallerMemberName to determine the calling property)

using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
 
public abstract class NotifyPropertyChanged : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
 
    protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(thisnew PropertyChangedEventArgs(propertyName));
        }
    }
 
    protected bool SetAndNotifyIfChanged<T>(ref T backingField, T newValue, 
        [CallerMemberNamestring propName = null)
    {
        if (EqualityComparer<T>.Default.Equals(backingField, newValue))
        {
            return false;
        }
 
        backingField = newValue;
        OnPropertyChanged(propName);
        return true;
    }
       
}

I also need a common extension method that extends INotifyPropertyChanged, which I’ll use in my unit tests to return observable changes in “known” properties.  This uses a compiled Expression that points to a property getter, rather a string representation of a class’s property (see FromPropertyChangedPattern(item => item.Status).Subscribe() in the unit tests)

NotifyPropertyChangedExtensions.cs:
using System;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reactive;
using System.Reactive.Linq;
using System.Reflection;
 
public static class NotifyPropertyChangedExtensions
{
    public static IObservable<TValue> FromPropertyChangedPattern<TSource, TValue>(
        this TSource source,
        Expression<Func<TSource, TValue>> property)
        where TSource : INotifyPropertyChanged
    {
        return 
           from _ in FromPropertyChangedPattern(source, GetPropertyInfo(property))
           let getter = property.Compile()
           select getter(source);
    }
 
    private static IObservable<EventPattern<PropertyChangedEventArgs>> FromPropertyChangedPattern(INotifyPropertyChanged source, PropertyInfo propertyInfo)
    {
        var changes = Observable.FromEventPattern<PropertyChangedEventHandlerPropertyChangedEventArgs>
            (eh => source.PropertyChanged += eh,
            eh => source.PropertyChanged -= eh);
 
        return changes
            .Where(e => string.Equals(e.EventArgs.PropertyName, propertyInfo.Name,                             StringComparison.Ordinal));
    }
 
    private static PropertyInfo GetPropertyInfo<TSource, TProperty>(
        Expression<Func<TSource, TProperty>> propertyLambda)
    {
        var type = typeof(TSource);
 
        var member = propertyLambda.Body as MemberExpression;
        if (member == null)
        {
            throw new ArgumentException(
                string.Format("[{0}] is a method, property expected"
                    propertyLambda));
        }
 
        var propInfo = member.Member as PropertyInfo;
        if (propInfo == null)
        {
            throw new ArgumentException(
                string.Format("[{0}] is field, property expected"
                    propertyLambda));
        }
 
        if (type != propInfo.ReflectedType && 
            propInfo.ReflectedType != null &&
            !type.IsSubclassOf(propInfo.ReflectedType))
        {
            throw new ArgumentException(
                string.Format("[{0}] unknown property"
                    propertyLambda));
        }
 
        return propInfo;
    }
}
 
 

Finally I’ll add IConnectablesStatusViewModel and IConnectableStatusItem.  These are interfaces to that represent the view model - I don’t want any classes referencing concrete classes as we’re using Separated Interfaces throughout.
 
using System;
using System.ComponentModel;
using System.Collections.Generic;
 
public interface IConnectableStatusItem : INotifyPropertyChanged
{
        string Description { get; }
        ConnectionStatus Status { get; }
        TimeSpan TimeStamp { get; }
}
 
public interface IConnectablesStatusViewModel 
{
        IEnumerable<IConnectableStatusItem> Connections { get; }
}

Task 2 – Using MEF to Discover Connectable Services (Part 1)

We’ve got the core interfaces defined, now it’s time to use looks at MEF’s ImportMany attribute.

By decorating a class’s constructor with the ImportingConstructor attribute we’re asking MEF to use this particular constructor when composing a part.  In addition, we can decorate any argument in the constructor like this: [ImportMany] IEnumerable<Lazy<T>> arg

This tells MEF to go off, at runtime, and find all objects that are marked with the [Export(typeof(T))] attribute.  Note the use of the Lazy class – this provides support for Lazy initialisation – without it you’ll get a composition exception.


Exactly how MEF determines which assemblies to go looking is very configurable – more on that later.

Turning to ConnectablesStatusViewModel, this is the main view model responsible for listening to the list of IConnectable objects and converting those status changes into an observable list of items that we can bind to in WPF.

I want MEF to create an instance of my ConnectablesStatusViewModel whenever any requests is made for an IConnectablesStatusViewModel, so I’ll decorate the class with Export(typeof(IConnectablesStatusViewModel)).   I’ll need to remember to use the corresponding Import attribute later on my WPF view:

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using ServiceDashboard.Core;
 
[Export(typeof(IConnectablesStatusViewModel))]
public class ConnectablesStatusViewModel : IConnectablesStatusViewModel
{
 [ImportingConstructor]
 public ConnectablesStatusViewModel([ImportManyIEnumerable<Lazy<IConnectable>> connectables)
 {}
 
 public IEnumerable<IConnectableStatusItem> Connections { getprivate set; }
}
 
 
When the constructor is called via MEF, I need to walk through each of the IConnectable instances passed in and listen for changes in each item’s Status property.

To do this, I’ll create a subscription to the Status observable, adding the resulting IDisposable object to a CompositeDisposable instance (making it easy to dispose all subscriptions in one call). 

private readonly CompositeDisposable _disposables = new CompositeDisposable();
 
public ConnectablesStatusViewModel(IEnumerable<Lazy<IConnectable>> connectables)
{
    var connections = new List<ConnectableStatusItem>();
 
    foreach (var connectable in connectables
        .Select(lazy => lazy.Value)
        .OrderBy(c => c.Description))
    {
        var statusItem = new ConnectableStatusItem(connectable.Description);
        connections.Add(statusItem);
 
        var subscription = connectable.Status
            .Subscribe(newStatus =>
            {
                statusItem.Status = newStatus;
                statusItem.TimeStamp = DateTime.Now.TimeOfDay;
            });
 
        _disposables.Add(subscription);
    }
 
    Connections = connections;
}
 
I loop through each of the connectable items (sorted for convenience by description), creating a ConnectableStatusItem item which takes the connectable’s Description as a constructor and then simply listen for a change in the connectable’s Status.
Note, I’m using List<ConnectableStatusItem> rather than an ObservableCollection< ConnectableStatusItem> as the list of connectables will not change as the app runs (I’m getting them all right here, right now).

That’s the guts of the ConnectablesStatusViewModel created.  I’ll extend the class to inherit from IDisposable to ensure my subscriptions are disposed of when Dispose is called on my View Model:

public void Dispose()
{
    _disposables.Dispose();
}

Task 3 – Using RX to Stem the Flow of Updates


So I’ve set up a subscription for each of the IConnectable instances discovered.  I could now set my ViewModel as the DataContext of a WPF window, but if any of the IConnectable instances repeatedly update their status, I’ll end up with that annoying flashing Christmas tree look, which I’m trying to prevent.

In the sample PriceServices project there are a number of dummy services configured to do just that; they update their status every 100 milliseconds or so:

protected FastTickingServiceBase(int millisecondTicks) 
{
        _tickSource = Observable
            .Interval(TimeSpan.FromMilliseconds(millisecondTicks))
            .Subscribe(_ => GenerateConnectionStatus());
}


Ideally I want to buffer these updates to  more usable number.  I’ve decided to use a window of one second for updates, so if multiple status updates arrive within that interval then I’ll just take the latest update, if any, and present that.

This is easily achieved using the Sample() extension method.  So I’ll change the subscription code to this:
const int SamplePeriodInSeconds = 1;
var sampleInterval = TimeSpan.FromSeconds(SamplePeriodInSeconds);
 
var subscription = connectable.Status
    .Sample(sampleInterval)
    .Subscribe(newStatus =>
    {
        statusItem.Status = newStatus;
        statusItem.TimeStamp = DateTime.Now.TimeOfDay;
    });


That’s it.  Although, there is one big step that I need to do if I want this to be unit testable …more on that later.

Gotcha: RX has a number of buffering-like extension methods, but you do need to be careful which one you choose based on the behaviour that you want.  Throttle() sounds ideal for my use case, but Throttle ignores any updates that happen repeatedly within your interval period.

Task 4 – Using MEF to Discover Connectable Services (Part 2)


I’ve created a ViewModel that, when given a list of IConnectable objects, will subscribe to status updates in each.  The flip side of this is the way in which we tell MEF how to go about looking for assemblies.  There are a number of ways to tell MEF how to do this, but one of my requirements was to use a plugins approach.  When the app starts up it should look in a predefined folder for any “extensions” assemblies.   

To achieve this I need to use a DirectoryCatalog passing in the path that I’m interested – MEF will then look for any .DLL files (by default) and parse the types in each assembly. 

As a convenience my ServiceDashboard.Presentation and PricingServices projects have a post-build event that copies the output into an Extensions folder in my WPF ServiceDashboard.UI app (this makes it easier to run the solution from Visual Studio).  Note, ServiceDashboard.UI does not have a direct project reference to either:

xcopy "$(TargetDir)*.*"  "$(SolutionDir)ServiceDashboard.UI\bin\$(ConfigurationName)\Extensions\" /Y

I’m going to use a simple implementation of the MefBootstrapper in conjunction with a basic Prism “shell”.   I’m only using the Prism Shell as a convenient way to initialise MEF, and to load up a specific WPF Window are runtime.  Normally you’d use a Shell with specific named regions, but that’s beyond the scope of this post.

NB: you’ll need to run the following into Package Manager to get the current Prism components from NuGet:

install-package Prism
install-package Prism.MEFExtensions

I’ve created a new WPF application, ServiceDashboard.UI, into which I’ve add a class called AppBootStrapper
 
using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.IO;
using System.Windows;
using Microsoft.Practices.Prism.MefExtensions;
 
class AppBootStrapper : MefBootstrapper
{
    protected override void ConfigureAggregateCatalog()
    {
        base.ConfigureAggregateCatalog();

        AggregateCatalog.Catalogs.Add(new AssemblyCatalog(GetType().Assembly));

        const string AddInPath = "Extensions";
        var extensionsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AddInPath);
        AggregateCatalog.Catalogs.Add(new DirectoryCatalog(extensionsPath));
    }

    protected override void InitializeShell()
    {
        Application.Current.MainWindow = (Shell)Shell;
        Application.Current.MainWindow.Show();
    }

    protected override DependencyObject CreateShell()
    {
        return Container.GetExportedValue<Shell>();
    }

    protected override void ConfigureContainer()
    {
        base.ConfigureContainer();

        Container.ComposeParts(this);
    }
}
 

I won’t go into too much detail with MefBootstrapper (will leave that for another post) - just the key methods that I need override.

ConfigureAggregateCatalog()


Here we tell MEF where to find assembles that are resolved using the Import/Export attributes.  I’m using AggregateCatalog.Catalogs.Add(new AssemblyCatalog(GetType().Assembly))  to include the current running WPF app (as it’s participating in part aggregation too).

More importantly I’ve using DirectoryCatalog to tell MEF to look in an “Extensions” subfolder for .DLL files:

const string AddInPath = "Extensions";
var extensionsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AddInPath);
AggregateCatalog.Catalogs.Add(new DirectoryCatalog(extensionsPath));


As mentioned above, my two of projects have a post-build event to copy build output to that folder.

InitializeShell()

I’ve created a WPF Window called Shell (coming up shortly).  This method will get called once MEF has been configured correctly.  I’m using it to make Shell the active window.

That’s the basics of the AppBootStrapper covered, but I still need to tell the WPF app to load and configure this boot strapper.  So rather than set a specific start up Window, I’ve overridden the OnStartup method in App.xaml.cs:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        //DispatcherUnhandledException += (sender, args) => Log("Dispatcher Unhandled exception", args.Exception);
            
        base.OnStartup(e);

        //TaskScheduler.UnobservedTaskException += HandleUnObservedTask;
        //AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;

        Mouse.OverrideCursor = Cursors.AppStarting;

        var splashScreen = new SplashScreen("/Assets/splash.jpg");
        splashScreen.Show(false);

        var bootstrapper = new AppBootStrapper();
        bootstrapper.Run();

        Mouse.OverrideCursor = null;

        splashScreen.Close(TimeSpan.FromSeconds(1));
    }
}

NB: I’ve commented out the common exception handlers that I usually attach to in a Release build. 

A splash screen .JPG resource is show using the Prism SpashScreen object (this is loaded prior to the time consuming WPF initialisation) and have guessed that it *might* take a second for MEF to finish and my Shell screen to be visible.

At the heart of the UI is the Shell.xaml.  As stated earlier, this is not a lesson on how to create a model Shell implementation, as such my Shell contains just a DataGrid that is bound to the a Connections property of my View Model

<Window x:Class="ServiceDashboard.UI.Shell"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:converters="clr-namespace:ServiceDashboard.UI.Converters"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:ui="clr-namespace:ServiceDashboard.UI"
        mc:Ignorable="d" 
        d:DataContext="{d:DesignInstance 
                       Type=ui:ConnectablesStatusViewModelDesignTime}"
        Title="Shell" Height="400" Width="400" 
        WindowStartupLocation="CenterScreen"
        Margin="2" WindowStyle="ToolWindow" >

        <DataGrid ItemsSource="{Binding Connections}" AutoGenerateColumns="False" Margin="8" Padding="4">
            <DataGrid.Resources>
              <converters:StatusToBrushConverter x:Key="statusToBrushConverter" />
            </DataGrid.Resources>
            
            <DataGrid.Columns>
                <DataGridTextColumn Header="Time" 
                    Binding="{Binding TimeStamp, StringFormat=hh\\:mm\\:ss\\.ff}"
                   
 MinWidth="40"/>
                
                <DataGridTextColumn Header="Status" 
                                    Binding="{Binding Status}"  
                                    MinWidth="90">
                    <DataGridTextColumn.ElementStyle>
                        <Style TargetType="{x:Type TextBlock}">
                            <Setter Property="Background" 
                                    Value="{Binding Status, Converter={StaticResource statusToBrushConverter}}"/>
                        </Style>
                </DataGridTextColumn.ElementStyle>
                    
                </DataGridTextColumn>
                <DataGridTextColumn Header="Description" 
                                    Binding="{Binding Description}"  
                                    MinWidth="180"/>
            </DataGrid.Columns>
        </DataGrid>
</Window>


I’ve created a Design Time instance of my View Model, ConnectablesStatusViewModelDesignTime.  This makes it easier to verify Bindings at design time:


class ConnectablesStatusViewModelDesignTime : IConnectablesStatusViewModel
{
    public IEnumerable<IConnectableStatusItem> Connections { getprivate set; }
}


I’ve also created a class StatusToBrushConverter.cs to convert the Connection Status to a known colour which is  applied to the Status TextColumn’s Background property:


<DataGrid.Resources>
    <converters:StatusToBrushConverter x:Key="statusToBrushConverter" />
</DataGrid.Resources>

<Style TargetType="{x:Type TextBlock}">
  <Setter Property="Background" 
    Value="{Binding Status, Converter={StaticResource statusToBrushConverter}}"/>
</Style>


[ValueConversion(typeof(ConnectionStatus), typeof(Color))]
public class StatusToBrushConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter,
                         
CultureInfo culture)
    {
        if (! (value is ConnectionStatus))
            return Binding.DoNothing;

        var status = (ConnectionStatus)value;
        switch (status)
        {
            case ConnectionStatus.Connected:
                return Brushes.LightGreen;

            case ConnectionStatus.Disconnected:
                return Brushes.Yellow;

            case ConnectionStatus.Error:
                return Brushes.Red;

            case ConnectionStatus.Unknown:
                return Brushes.Gainsboro;

            default:
                return DependencyProperty.UnsetValue;
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter,
                             
CultureInfo culture)
    {
        return Binding.DoNothing;
    }
}


I still need to register my Shell class with MEF, so I’ve decorated Shell.xaml.cs with Export/Import attributes:

[Export]
public partial class Shell : Window
{
    public Shell()
    {
        InitializeComponent();
    }

    [Import]
    public IConnectablesStatusViewModel ViewModel
    {
        set { DataContext = value; }
    }
}


At runtime, MEF will look for an item in its catalog which satisfies the IConnectablesStatusViewModel Import/Export.  In this case it will be a shared concrete instance of ConnectablesStatusViewModel.   The Setter will use that to set the Window’s DataContext – which the DataGrid is bound to.

Task 5 – Unit Testing

There’s a whole list of things that need to be unit tested, but for this demo I’ll just concentrate on the ConnectablesStatusViewModel.

I have a Unit Test project , ServiceDashboard.UnitTest, which uses a couple of NuGet packages:

FakeItEasy
NUnit
Rx-Testing
Rx-PlatformServices


In this project I have ConnectablesStatusViewModelTests (in the Presentation folder).  Because I’m using the Sample extension method, I’ll need to make some changes to ConnectablesStatusViewModel in order to gain control of time - remember that we’re buffering updates into time slices, so I’ll need to make use of an IScheduler instance from the RX concurrency namespace.

In the unit tests I’m going to use a TestScheduler instance, in the real WPF application I’d use the DispatcherScheduler.Current instance as a scheduler.


So back to ConnectablesStatusViewModel. I’m going to add a new argument to the constructor - IScheduler scheduler and use the Sample overload which takes an interval AND a scheduler instance:

public ConnectablesStatusViewModel(IEnumerable<Lazy<IConnectable>> connectables, 

                                   IScheduler scheduler)
{
    var connections = new List<ConnectableStatusItem>();
 
    const int SamplePeriodInSeconds = 1;
    var sampleInterval = TimeSpan.FromSeconds(SamplePeriodInSeconds);
 
    foreach (var connectable in connectables
        .Select(lazy => lazy.Value)
        .OrderBy(c => c.Description))
    {
        var statusItem = new ConnectableStatusItem(connectable.Description);
        connections.Add(statusItem);
                
        var subscription = connectable.Status
            .Sample(sampleInterval, scheduler)
            .Subscribe(newStatus =>
            {
                statusItem.Status = newStatus;
                statusItem.TimeStamp = DateTime.Now.TimeOfDay;
            });
 
        _disposables.Add(subscription);
    }
 
    Connections = connections;
}


By doing this, I’m asking RX to use a specific Scheduler to run the sampling timer on.  The TestScheduler allows me to move time forwards to confirm that I’m only getting a specific number of updates per interval.


As I’m using MEF and an ImportingConstructor attribute I ideally need to expose a part that returns the DispatcherScheduler.Current for a named Scheduler export, for the time being I’ve created a constructor overload which is used by MEF and keep the second constructor which I’ll access via my unit tests.


[ImportingConstructor]
public ConnectablesStatusViewModel([ImportManyIEnumerable<Lazy<IConnectable>> connectables)
    : this(connectables, DispatcherScheduler.Current)
{ }


public ConnectablesStatusViewModel(IEnumerable<Lazy<IConnectable>> connectables, IScheduler scheduler)
{….}


Back to the unit tests,  I’ve added a new test with the interesting title  MultipleConnectables_ConnectionsCountEqualsConnectablesCount.  My test asserts that the count of connectables passed into the view model matches the count of the Connections property.


I have a couple of static helper methods:


CreateConnectable():  creates a fake connectable instance based on IConnectable using FakeItEasy’s A.Fake static method.


CreateViewModel(): creates a concrete ConnectablesStatusViewModel instance with the specified List of connectables and the TestScheduler.  Although I’m passing a TestScheduler, I’m not actually using that as part of this test.


[TestFixture]
public class ConnectablesStatusViewModelTests
{
 
    [Test]
    public void MultipleConnectables_ConnectionsCountEqualsConnectablesCount()
    {
        // ARR
        var connectables = new List<Lazy<IConnectable>>
        {
            new Lazy<IConnectable>(CreateConnectable),
            new Lazy<IConnectable>(CreateConnectable),
            new Lazy<IConnectable>(CreateConnectable)
        };
 
        // ACT
        var vm = CreateViewModel(connectables, new TestScheduler());
 
        // ASS
        Assert.That(vm.Connections.Count(), Is.EqualTo(connectables.Count));
    }
}

private static ConnectablesStatusViewModel CreateViewModel(IEnumerable<Lazy<IConnectable>> connectables, IScheduler scheduler)
{
    return new ConnectablesStatusViewModel(connectables, scheduler);
}
 
private static IConnectable CreateConnectable()
{
    return CreateConnectable(new Subject<ConnectionStatus>());
}
 
private static IConnectable CreateConnectable(IObservable<ConnectionStatus> subject)
{
    var connectable = A.Fake<IConnectable>();
 
    A.CallTo(() => connectable.Status)
        .Returns(subject);
 
    return connectable;
}


My next test is more interesting.  I create a single Connectable instance, but use a Subject<ConnectionStatus> (statusSubject ) which allows me to explicitly fire status changes.


As there’s only one connectable instance, I retrieve the first ConnectableStatusItem from the Connections property.  From this I subscribe to changes in the Status using my FromPropertyChangedPattern extension method, to increment a simple counter when that property changes:


connectionStatusItem.FromPropertyChangedPattern(item => item.Status)
  .Subscribe(_ => statusChangedCount++);



 [Test]
public void MultipleStatusChanges_FiresStatusPropertyChangedOncePerInterval()
{
    // ARR
    var statusSubject = new Subject<ConnectionStatus>();
 
    var connectables = new List<Lazy<IConnectable>>
    {
        new Lazy<IConnectable>( () => CreateConnectable(statusSubject))
    };
 
    var scheduler = new TestScheduler();
    var vm = CreateViewModel(connectables, scheduler);
    var connectionStatusItem = vm.Connections.First();
 
    var statusChangedCount = 0;
          
    connectionStatusItem.FromPropertyChangedPattern(item => item.Status)
        .Subscribe(_ => statusChangedCount++);
 
    // ACT
    var testStatuses = Enum.GetValues(typeof(ConnectionStatus))
        .Cast<ConnectionStatus>()
        .ToList();
    for (var i = 0; i < 10; i++)
    {
        foreach (var status in testStatuses)
        {
            statusSubject.OnNext(status);
        }
    }
 
    // verify that we've not yet had an property changes
    Assert.That(statusChangedCount, Is.EqualTo(0));
 
    // Wind the clock forward a bit
    var halfInterval = TimeSpan.FromSeconds(0.5).Ticks;
    scheduler.AdvanceBy(halfInterval);
    Assert.That(statusChangedCount, Is.EqualTo(0));
 
    scheduler.AdvanceBy(halfInterval);
    Assert.That(statusChangedCount, Is.EqualTo(1));
}


The For loop represents many status updates firing.  Remember that we have a rule which states multiple status updates should be buffered into once per second.  Using the TestScheduler I can effectively wind the clock to represent a change in time, using the AdvanceBy method. 


So I wind the clock forward by half a second and assert that the status change event has not yet fired.


Again I forward time by half a second, this gets us past our sampling interval, no matter how many status update have fired, and I’ve only received one property change.

That's it.  Source Code can be found here:

No comments:

Post a Comment