20

How should I use RxJava's TestScheduler? I come from a .NET background but the TestScheduler in RxJava does not seem to work the same way as the test scheduler in .NET rx.

Here is sample code that I want to test

Observable<Long> tick = Observable.interval(1, TimeUnit.SECONDS);
contactsRepository.find(index)
  .buffer(MAX_CONTACTS_FETCH)
  .zipWith(tick, new Func2<List<ContactDto>, Long, List<ContactDto>>() {
    @Override
    public List<ContactDto> call(List<ContactDto> contactList, Long aLong) {
      return contactList;
    }
  }).subscribe()

I've tried:

subscribeOn(testScheduler)
testScheduler.advanceTimeBy(2, TimeUnit.SECONDS);
testScheduler.triggerActions();

with no luck.

stkent
  • 19,772
  • 14
  • 85
  • 111
Calin
  • 6,661
  • 7
  • 49
  • 80

2 Answers2

26

I made a little example of how to use a TestScheduler. I think it's very similar to the .NET implementation

@Test
public void should_test_the_test_schedulers() {
    TestScheduler scheduler = new TestScheduler();
    final List<Long> result = new ArrayList<>();
    Observable.interval(1, TimeUnit.SECONDS, scheduler)
        .take(5)
        .subscribe(result::add);
    assertTrue(result.isEmpty());
    scheduler.advanceTimeBy(2, TimeUnit.SECONDS);
    assertEquals(2, result.size());
    scheduler.advanceTimeBy(10, TimeUnit.SECONDS);
    assertEquals(5, result.size());
}

https://github.com/bric3/demo-rxjava-humantalk/blob/master/src/test/java/demo/humantalk/rxjava/SchedulersTest.java

EDIT According to your code : you should pass the scheduler to the Observable.interval operation, as this is what you want to control :

    TestScheduler scheduler = new TestScheduler();

    Observable<Long> tick = Observable.interval(1, TimeUnit.SECONDS, scheduler);
    Subscription toBeTested = Observable.from(Arrays.asList(1, 2, 3, 4, 5))
            .buffer(3)
            .zipWith(tick, (i, t) -> i)
            .subscribe(System.out::println);

    scheduler.advanceTimeBy(2, TimeUnit.SECONDS);
Nicolas Filotto
  • 43,537
  • 11
  • 94
  • 122
dwursteisen
  • 11,435
  • 2
  • 39
  • 36
  • 3
    Yes, this is great, but how about testing a 'real/ piece of code, let's stay you have a method with a Observable subscriber that executes some callback onComplete and some other callback onError. I've injected the test scheduler in `subscribeOn` but no luck from there. – Calin Nov 02 '14 at 14:05
13

you have some class:

public class SomeClass {
  public void someMethod() {
    Observable<Long> tick = Observable.interval(1, TimeUnit.SECONDS);
    contactsRepository.find(index)
      .buffer(MAX_CONTACTS_FETCH)
      .zipWith(tick, new Func2<List<ContactDto>, Long, List<ContactDto>>() {
        @Override
        public List<ContactDto> call(List<ContactDto> contactList, Long aLong) {
          return contactList;
        }
      }).subscribe()
  }
}

Look up [Observable.interval][1] in the docs and you will see it operates on the computation scheduler, so lets override that in our test.

public class SomeClassTest {
  private TestScheduler testScheduler;

  @Before
  public void before() {
    testScheduler = new TestScheduler();
    // set calls to Schedulers.computation() to use our test scheduler
    RxJavaPlugins.setComputationSchedulerHandler(ignore -> testScheduler);
  }

  @After
  public void after() {
    // reset it
    RxJavaPlugins.setComputationSchedulerHandler(null);
  }

  @Test
  public void test() {
    SomeClass someInstance = new SomeClass();
    someInstance.someMethod();

    // advance time manually
    testScheduler.advanceBy(1, TimeUnit.SECONDS);
  }

This solution is an improvement to the accepted answer as the quality, integrity and simplicity of the production code is maintained.

Andrew G
  • 2,596
  • 2
  • 19
  • 26