简体   繁体   中英

When using TestScheduler to fire events into Observable.FromEventPattern that has an ObserveOn, the events aren't observed until next event is fired

I seem to be having a problem with a particular setup of using TestScheduler with The Observable.FromEventPattern that has an ObserveOn. What seems to be happening is that the events are both fired but I only observe the first event when the second event has fired. Of course I could have done something completely silly here and I just can't see what I am doing wrong. Also I may have completely missed a point or trick :-)

Could someone explain to me what I am missing in knowledge or what I am doing wrong to only see 1 event, if I advance by 2?

I have read most of my information on Lee Campbells http://www.introtorx.com/ (which I have found a superb fountain of knowledge :-) )

I am using:

Moq V4.2.1409.1722

Rx V 2.2.5.0

XUnit V 1.9.2.1705

This is my code. Just my own event args so I can watch the piece of data that is being observed.

public class MyEventArgs : EventArgs
{
    public int Data { get; private set; }

    public MyEventArgs(int data)
    {
        Data = data;
    }
}

An interface with an EventHandler which will be mocked.

public interface ITmp
{
    event EventHandler<MyEventArgs> tmpEvent;
}

The class that has the observer and monitors the events from the tmp object passed in, it also takes a scheduler so I can test this observer.

internal class SomeClass2
{
    private IObservable<EventPattern<MyEventArgs>> _observable;

    public SomeClass2(ITmp tmp, IScheduler scheduler)
    {
        _observable = Observable.FromEventPattern<MyEventArgs>(h => tmp.tmpEvent += h, h => tmp.tmpEvent -= h)
                                .Do(next => Console.WriteLine("Item came in...{0}", next.EventArgs.Data))
                                .ObserveOn(scheduler);
    }

    public IObservable<EventPattern<MyEventArgs>> Raw()
    {
        return _observable;
    }
}

The test.

public class Tests
{
    [Fact]
    public void FactMethodName2()
    {
        var mockedTmp = new Mock<ITmp>();
        var testScheduler = new TestScheduler();
        var temp = new SomeClass2(mockedTmp.Object, testScheduler);
        var count = 0;
        var myEventArgsObserved = new List<MyEventArgs>();

        temp.Raw().Subscribe(
            next =>
            {
                count++;
                myEventArgsObserved.Add(next.EventArgs);
            });

        testScheduler.Schedule(TimeSpan.FromTicks(1), () => mockedTmp.Raise(tmp => tmp.tmpEvent += null, new MyEventArgs(1)));
        testScheduler.Schedule(TimeSpan.FromTicks(2), () => mockedTmp.Raise(tmp => tmp.tmpEvent += null, new MyEventArgs(2)));

        testScheduler.AdvanceBy(1);
        testScheduler.AdvanceBy(1);

        Assert.Equal(2, count);
    }
}

Console Output:

Item came in ...1

Item came in ...2

The assert fails as it has only observed 1 event at this point. I have stepped through this and have observed that the first event is not observed until the second event has fired.

As a side note this test will work if I add another AdvanceBy(1) or if I use testScheduler.Start instead of the AdvanceBy's, or if I remove the ObserveOn and pass the scheduler into the FromEventPattern.

This code is behaving exactly as I would expect.

In your test, you schedule two actions on the test scheduler to raise events. One at time T=1 and one at time T=2.

Then you advance the test scheduler to time T=1. At this point, the test scheduler examines it's scheduled actions to see what needs to be run. It will select the first action to raise the event.

This event will be fired and picked up by the observable subscriber to the event - which is the subscription due to the ObserveOn operator. This will then schedule a call to raise an OnNext to it's subscriber on the test scheduler as soon as possible - the test scheduler will not execute this scheduled action until time is next advanced .

This is absolutely by design. The intention is to be able to control and observe the cascade of operations and simulate the reality that it takes some amount of time for Rx events to be scheduled and executed. You really wouldn't want it any other way.

So on the next timeslice, T=2, the second event is raised first (it was scheduled first) and then the OnNext call for the first event is fired on the subscriber to the ObserveOn . And so on.

Think of it like this - every action passing through a scheduler costs at least one unit of time.

To see this, if you remove the ObserveOn line, you remove the intermediate scheduling and the test as written will pass.

Quite often when writing reactive tests, you need to take account of these effects and adjust your assertions. For this reason, as a best practice I would advise scheduling actions at least 1000 ticks apart. I tend to get ticks with expressions like TimeSpan.FromSeconds(x).Ticks and often have asserts like TimeSpan.FromSeconds(x).Ticks + epsilon where epsilon is some expected small constant.

I have also used helper methods to assert the time is within some expected small range of ticks from a stated point - this can help make tests more readable and avoid having to adjust everything when you make subtle changes.

So all that said, a more idiomatic way to write your test would be as follows:

public class Tests : ReactiveTest
{
    [Fact]
    public void FactMethodName2()
    {
        var mockedTmp = new Mock<ITmp>();
        var testScheduler = new TestScheduler();
        var temp = new SomeClass2(mockedTmp.Object, testScheduler);
        const int c = 1;

        var eventArgs1 = new MyEventArgs(1);
        var eventArgs2 = new MyEventArgs(2);

        var results = testScheduler.CreateObserver<MyEventArgs>();

        temp.Raw().Select(ep => ep.EventArgs).Subscribe(results);

        testScheduler.Schedule(TimeSpan.FromTicks(1000),
            () => mockedTmp.Raise(tmp => tmp.tmpEvent += null, eventArgs1));

        testScheduler.Schedule(TimeSpan.FromTicks(2000),
            () => mockedTmp.Raise(tmp => tmp.tmpEvent += null, eventArgs2));

        testScheduler.Start();

        results.Messages.AssertEqual(
            OnNext(1000 + c, eventArgs1),
            OnNext(2000 + c, eventArgs2));
    }
}

Here we derive the test class from ReactiveTest which gives as the OnNext helper method used in the Assert. Using Start rather than AdvanceBy runs the test scheduler until the scheduler queue is empty. Using the test scheduler created observer ( results ) lets us record events and when they occured easily, and easily assert for that.

Note you can use a predicate to test the recorded event in place of the event payload for more complex tests - eg instead of using eventArgs1 in the assertion you could do something like args => args.Data == 1 . Here I simplified the check by Select ing out the event payload in my subscription to enable a more readable equality check.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM