Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing higher-order Observables #144

Merged
merged 5 commits into from Mar 10, 2017
Merged

Testing higher-order Observables #144

merged 5 commits into from Mar 10, 2017

Conversation

martinsik
Copy link
Contributor

@martinsik martinsik commented Feb 15, 2017

I was implementing the window() operator and I realized I've encountered a situation that wasn't tested before. That's higher-order Observables and in particular operators that emit Observables where I need to test that these Observables emit correct items themselves.

This PR lets me write tests for window() operator like the following:

$xs = $this->createHotObservable([
    onNext(225, 1),
    onNext(250, 2),
    onNext(300, 3),
    ...
]);

$results = $this->scheduler->startWithCreate(function() use ($xs, $window) {
    return $xs->window($window);
});

$window = $this->createHotObservable([
    onNext(325, 1),
   ...
]);

$this->assertMessages([
    onNext(200, $this->createColdObservable([
        onNext(25, 1),
        onNext(50, 2),
        onNext(100, 3),
        onCompleted(125),
    ])),
    ...

https://github.com/martinsik/RxPHP/blob/window-operator/test/Rx/Functional/Operator/WindowTest.php

The most important part is this:

onNext(200, $this->createColdObservable([
    onNext(25, 1),
    onNext(50, 2),
    onNext(100, 3),

There's a new OnNextObservableNotification that automatically subscribes to an Observable and all items are recorded with timestamps relative to its parent. Then these automatic subscriptions are ignored by the ColdObservable. I've actually spent quite some time scratching my head around how to implement this in the most comfortable and easy to use way. Ideally if I didn't need to think about it at all and use Observables just like any other values.

If I didn't miss anything this should allow fluent porting from RxJS 5 marble tests (eg. window-spec.ts) where they treat Observables like values. For example this should work in RxPHP as well:

const expected =     'x------------y------------z------------|    ';
const x = cold(      '---a---b---c-|                              ');
const y = cold(                   '--d---e---f--|                 ');
const z = cold(                                '-g---h---i---|    ');
const expectedValues = { x: x, y: y, z: z };

Using this with #133 shouldn't be any problem.

Also, I didn't need to modify any existing tests including those that use higher-order Observables as input such as MergeAllTest.php.

@coveralls
Copy link

coveralls commented Feb 15, 2017

Coverage Status

Coverage increased (+0.1%) to 95.992% when pulling 0a0a691 on martinsik:testing-higher-order-observables into 84bfbd0 on ReactiveX:master.

@martinsik
Copy link
Contributor Author

martinsik commented Feb 15, 2017

As a proof of concept I cherry picked commits from #133 and my window() operator and made marble tests for window() operator just to see whether it really works the way I think it should.

https://github.com/martinsik/RxPHP/blob/marble-tests-window-operator/test/Rx/Functional/Operator/WindowTest.php

I had to make a few small changes to FunctionalTestCase class in the way it handles marbles and I also added parsing subscriptions. I'll make appropriate comments into #133.

For example this test is based on window-spec.ts and is passing:

/**
 * @test
 */
public function should_emit_windows_that_close_and_reopen()
{
    $source = $this->createHot( '---a---b---c---d---e---f---g---h---i---|');
    $sourceSubs =               '^                                      !';
    $window = $this->createHot( '-------------w------------w------------|');
    $windowSub =                '^                                      !';
    $expected =                 'x------------y------------z------------|';
    $x      = $this->createCold('---a---b---c-|                          ');
    $y      = $this->createCold(             '--d---e---f--|             ');
    $z      = $this->createCold(                          '-g---h---i---|');
    $expectedValues = [ 'x' => $x, 'y' => $y, 'z' => $z ];

    $results = $this->scheduler->startWithCreate(function() use ($source, $window) {
        return $source->window($window);
    });

    $expectedMessages = $this->convertMarblesToMessages($expected, $expectedValues, null, TestScheduler::SUBSCRIBED);
    $this->assertMessages($expectedMessages, $results->getMessages());

    $this->assertSubscriptions($sourceSubs, $source->getSubscriptions(), TestScheduler::SUBSCRIBED);
    $this->assertSubscriptions($windowSub, $window->getSubscriptions(), TestScheduler::SUBSCRIBED);
}

Some things are a bit clumsy. I had to manually tell when the subscription happens with TestScheduler::SUBSCRIBED because that's predefined by startWithCreate(). Otherwise it thinks it subscribes at 0.
Using startWithTiming($create, 0, 0) is probably not a good options because then the TestScheduler count +1 automatically. I guess this is solvable somehow better that hardcoding TestScheduler::SUBSCRIBED like I did.

@martinsik martinsik mentioned this pull request Feb 15, 2017
@mbonneau mbonneau merged commit fe15af7 into ReactiveX:master Mar 10, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants