java - 我如何测试这个 rxJava 代码?

标签 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()

第一点很容易用 Mockito.verify() 测试。第三点我可以注入(inject)调度程序并使用 Schedulers.immediate(),然后使用 Subject 模拟观察者并检查 Subject.hasObservers()。但是我不知道如何测试第二点。

我如何测试这段代码?我必须重构它吗?怎么办?

请认为 filter 只是一个例子,我有一个包含不同运算符的大链。

最佳答案

很难测试此方法,因为没有可断言的“可观察”行为,您需要让测试代码“妨碍”逻辑。

这里有一个您可以遵循的简单方法:(尽管您可能想考虑将事情分解以使测试更容易)

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

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

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

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

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

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 ...
    }
}

关于java - 我如何测试这个 rxJava 代码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41396032/

相关文章:

jvm - 有没有独立的 JAVA 可以在没有任何操作系统的 PC 上运行

testing - 如何在嵌入式系统上检查堆和栈 RAM 的一致性

ios - Xcode 6 测试目标 : Unidentified Symbols for Architecture

android - 在 RxJava 中为 Android 中的 OnCompleted 传递参数

java - 激活页面时,sling 事件处理程序仅响应一次

java - 为什么 StringBuffer/StringBuilder 不覆盖 equals 或 hashCode?

android - 在RxJava中使用 "skipWhile"结合 "repeatWhen"实现服务器轮询

java - 当我们连接两个可观察的重复项时,输出流中的重复项会被消除吗?

Java Logger 不记录到控制台

testing - 让微软测试框架使用 Spring.Net 创建测试类