我该如何测试这个rxJava代码?

时间:2016-12-30 12:08:35

标签: java testing rx-java reactive-programming

void start() {
    bar.foo()
        .filter(i -> i % 2 == 0)
        .subscribeOn(computation())
        .observeOn(io())
        .subscribe(new FooSubscriber());
}

在这个功能中,我看到3点要测试:

  1. 确认我致电bar.foo()
  2. 验证filter是否已正确实施。
  3. 确认我已订阅bar.foo()
  4. 第一点很容易使用Mockito.verify()进行测试。第三点我可以注入调度程序并使用Schedulers.immediate()然后使用Subject模拟观察者并检查Subject.hasObservers()。但我不知道如何测试第二点。

    如何测试此代码?我必须重构吗?怎么样?

    请认为filter只是一个例子,我有一个不同运营商的大链。

1 个答案:

答案 0 :(得分:1)

难以测试此方法,因为没有"可观察的"断言的行为,你需要得到你的测试代码""逻辑。

这是一个您可以遵循的简单方法:(尽管您可能需要考虑拆分以使测试更容易)

  1. 模拟foo,验证是否需要调用bar(),从bar()返回一个真正的Observable,它将在调用subscribe时展开回调链。 - 这将测试您的链条是否按预期连接。

  2. 注入以阻塞方式在主线程上执行逻辑的调度程序,从而使测试保持同步且易于理解

  3. new FooSubscriber()提取到包私有方法并使用Mockito监视新方法,返回一个测试订阅者,对从observable发出的过滤数据进行断言 - 或 - 注入构建的工厂类FooSubscriber的实例,您可以通过返回测试订阅者来模拟测试目的。 - 基本上,新关键字的硬编码使用会阻止您测试行为。

  4. 如果您需要,我可以提供示例,希望这能让您前进。

    编辑:上述两种方法的例子:

    package com.rx;
    
    import org.junit.Assert;
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.Mockito;
    import org.mockito.runners.MockitoJUnitRunner;
    import rx.Observable;
    import rx.Observer;
    import rx.Scheduler;
    import rx.schedulers.Schedulers;
    
    import java.util.ArrayList;
    import java.util.List;
    
    @RunWith(MockitoJUnitRunner.class)
    public class TestRxMethod {
    
        // prod Bar class - this class tested in isolation in different test.
        public static class Bar {
    
            public Observable<Integer> foo() {
                return null;
            }
        }
    
        // prod FooSubscriber class - this class tested in isolation in different test.
        public static class FooSubscriber implements Observer<Integer> {
    
            @Override
            public void onCompleted() {
            }
    
            @Override
            public void onError(Throwable e) {
            }
    
            @Override
            public void onNext(Integer t) {
            }
        }
    
        // prod FooSubscriberFactory class - this class tested in isolation in different test.
        public static class FooSubscriberFactory {
    
            public Observer<Integer> getInstance() {
                return null;
            }
        }
    
        // prod "class under test"
        public static class UnderTest {
            private final Bar bar;
            private final Scheduler computationScheduler;
            private final Scheduler ioScheduler;
            private final FooSubscriberFactory fooSubscriberFactory;
    
            public UnderTest(Bar bar, Scheduler computationScheduler, Scheduler ioScheduler,
                    FooSubscriberFactory fooSubscriberFactory) {
                this.bar = bar;
                this.computationScheduler = computationScheduler;
                this.ioScheduler = ioScheduler;
                this.fooSubscriberFactory = fooSubscriberFactory;
            }
    
            public void start() {
                //@formatter:off
                bar.foo()
                    .filter(i -> i.intValue() % 2 == 0)
                    .subscribeOn(computationScheduler)
                    .observeOn(ioScheduler)
                    .subscribe(fooSubscriber());
                //@formatter:on
            }
    
            // package private so can be overridden by unit test some drawbacks
            // using this strategy like class cant be made final. - use only
            // if cant restructure code.
            Observer<Integer> fooSubscriber() {
                return fooSubscriberFactory.getInstance();
            }
        }
    
        // test Foo subscriber class - test will put set an instance of
        // this class as the observer on the callback chain.
        public static class TestFooSubscriber implements Observer<Integer> {
    
            public List<Integer> filteredIntegers = new ArrayList<>();
    
            @Override
            public void onCompleted() {
                // noop
            }
    
            @Override
            public void onError(Throwable e) {
                // noop
            }
    
            @Override
            public void onNext(Integer i) {
                // aggregate filtered integers for later assertions
                filteredIntegers.add(i);
            }
        }
    
        // mock bar for test
        private Bar bar;
    
        // mock foo subscriber factory for test
        private FooSubscriberFactory fooSubscriberFactory;
    
        // class under test - injected with test dependencies
        private UnderTest underTest;
    
        @Before
        public void setup() {
            bar = Mockito.mock(Bar.class);
            fooSubscriberFactory = Mockito.mock(FooSubscriberFactory.class);
            underTest = new UnderTest(bar, Schedulers.immediate(), Schedulers.immediate(), fooSubscriberFactory);
        }
    
        // Option #1 - injecting a factory
        @Test
        public void start_shouldWork_usingMockedFactory() {
            // setup bar mock to emit integers
            Mockito.when(bar.foo()).thenReturn(Observable.just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
    
            // setup the subscriber factory to produce an instance of the test subscriber
            TestFooSubscriber testSubscriber = new TestFooSubscriber();
            Mockito.when(fooSubscriberFactory.getInstance()).thenReturn(testSubscriber);
    
            underTest.start();
    
            Assert.assertEquals(5, testSubscriber.filteredIntegers.size());
            // ... add more assertions as needed per the use cases ...
        }
    
        // Option #2 - spying a protected method
        @Test
        public void start_shouldWork_usingSpyMethod() {
            // setup bar mock to emit integers
            Mockito.when(bar.foo()).thenReturn(Observable.just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
    
            // spy the class under test (use only as a last resort option)
            underTest = Mockito.spy(underTest);
            TestFooSubscriber testSubscriber = new TestFooSubscriber();
            Mockito.when(underTest.fooSubscriber()).thenReturn(testSubscriber);
    
            underTest.start();
    
            Assert.assertEquals(5, testSubscriber.filteredIntegers.size());
            // ... add more assertions as needed per the use cases ...
        }
    }