Wednesday, December 21, 2011

Preparing for unit testing with testable observables

In the following example we are going to explore the basic functionality provided to help us with unit testing of RX observables. First we have to create an instance of the class TestScheduler which implements ISchudeler interface. This will enable us to schedule some work for the future. Then we call the CreateObserver method on it. It will return us an ITestableObserver. There is a property called Messages which is an addition to the classical IObservable interface. It will contain the actions sent to the previously created MockObserver after calling the Start method on the testScheduler. Next we will schedule two notifications. There are three possibilities in the NotificationKind enumeration (OnNext, OnError and OnCompleted). You can create these with Notification.CreateOnNext, Notification.CreateOnError and Notification.CreateOnCompleted static methods. These actions are scheduled to run after 100 and 200 virtual ticks. Schedule contains a callback which will be executed when the virtual time will be advanced to this point. In this case we are calling the notification's Accept method which invokes the mock observer's method corresponding to the notification. In our case it's OnNext for the our first scheduled item and for the second one it's OnCompleted. After we have subscribed to the testable observable we can start the scheduler. This will actually run our scheduled items an send them to the mock observer. This observer will record the received notifications to it's Messages collection.
    var testScheduler = new TestScheduler();
    
    var testableObserver = testScheduler.CreateObserver<int>();
    
    testScheduler.ScheduleAbsolute(Notification.CreateOnNext<int>(2), 100L, (scheduler, state) => {
        state.Accept(testableObserver);
        return Disposable.Empty;
    });
    
    testScheduler.ScheduleAbsolute(Notification.CreateOnCompleted<int>(), 200L, (scheduler, state) => {
        state.Accept(testableObserver);
        return Disposable.Empty;
    });
    
    testScheduler.Start();
    
    foreach (var message in testableObserver.Messages)
    {
        Console.WriteLine("Value {0} at {1}", message.Value, message.Time);
    }
Finally we will send the recorded messages to the output:
================
Value OnNext(2) at 100
Value OnCompleted() at 200
================
We can achieve the same result with the use of CreateColdObservable method. It receives as an input a collection of notification records. Record's Value has to be a Notification which are in this case created with the help of static methods ReactiveTest.OnNext and ReactiveTest.OnCompleted (there also exists a ReactiveTest.OnError). In case of OnNext the first parameter is the relative schedule time and the value of the notification.
    var testScheduler = new TestScheduler();            
    
    var records = new Recorded<Notification<int>>[] {
        ReactiveTest.OnNext(100, 1),
        ReactiveTest.OnCompleted<int>(200)
    };
    
    var testableObserver = testScheduler.CreateObserver<int>();
    var testableObservable = testScheduler.CreateColdObservable(records);
    
    testableObservable.Subscribe(testableObserver);
    
    testScheduler.Start();
    
    foreach (var message in testableObserver.Messages)
    {
        Console.WriteLine("Value {0} at {1}", message.Value, message.Time);
    }
Yet another way how to achieve the same thing is to provide a create observable method and provide creation, subscription and disposal time to the Start method of the test scheduler. In this case the notifications are scheduled relatively to the subscription time. So now we don't have to create a mock observer manually and subscribe to the test observable, it will be managed by the scheduler.
    var testScheduler = new TestScheduler();            
    
    var records = new Recorded<Notification<int>>[] {
        ReactiveTest.OnNext(100, 1),
        ReactiveTest.OnCompleted<int>(200)
    };
    
    var testableObserver = testScheduler.Start(
        () => testScheduler.CreateColdObservable(records),
        0, 50, 300
    );
    
    foreach (var message in testableObserver.Messages)
    {
        Console.WriteLine("Value {0} at {1}", message.Value, message.Time);
    }
The results are:
================
Value OnNext(1) at 150
Value OnCompleted() at 250
================
That's it for now. Next we will explore subjects and the difference between cold and hot observables.

No comments:

Post a Comment