简体   繁体   中英

How unit test multithreaded Android RxJava

Suppose there is a button.

Clicking the button disables it ( mainThread thread), starts a background task to load data ( IO thread). Once data is loaded, the button is enabled back ( mainThread thread).

For test, it's common to change schedulers to immediate , but this won't work in my case - button click will block until completion of background task, I'll never be able to check if the button was disabled after it started background task.

Besides unit tests, I'd also like to test this in functional Espresso tests.

How do I test this multithreaded RxJava case?

You can write your own ThreadFactory

ThreadFactory custom = new CustomThreadFactory();
ExecutorService executorService = Executors.newCachedThreadPool(custom); //or use newSingleThreadExecutor(..)
Scheduler customScheduler = Schedulers.from(executorService);

now you can use this scheduler and not block the main queue plus getting called when a new thread is needed:

class CustomThreadFactory implements ThreadFactory {
  public Thread lastT;
  public int newThreadCounter = 0;
  @Override
  public Thread newThread(Runnable r) {
    newThreadCounter++;
    System.out.println("newThread called");
    Thread lastT = new Thread(r); //or CustomThread(r)
    return lastT;
  }
}

You can even go further and instrument the new Thread -

 class CustomThread extends Thread {
    public CustomThread(Runnable r) {
      super(r);
    }
    @Override
    public void run() {
      System.out.printf("About to run!");
      super.run();
    }
  }
}

I suggest you to use RxUtil

1). Provide default implementation of RxUtil through the constructor or DI

2). When you create your observable, instead of applying schedulers directly:

    .subscribeOn(Schedulers.newThread())
    .observeOn(AndroidSchedulers.mainThread())

Use RxUtil :

.compose(rxUtil.applySchedulers())

Example:

https://github.com/DAYTeam/GoEnglish/blob/master/app/src/main/java/ru/goenglish/goenglish/services/impl/ScheduleServiceImpl.java#L38-L44

3). In unit tests, instead of default implementation of RxUtil , create mocked version:

public class UnitTestRxUtil implements RxUtil {
    @Override
    public <T> Observable.Transformer<T, T> applySchedulers() {
        return observable -> observable.subscribeOn(Schedulers.immediate())
                .observeOn(Schedulers.immediate());
    }
}

link: https://github.com/DAYTeam/GoEnglish/blob/master/app/src/test/java/ru/goenglish/goenglish/util/UnitTestRxUtil.java

4). Pass this implementation through the constructor, or DI.
Example (constructor): https://github.com/DAYTeam/GoEnglish/blob/master/app/src/test/java/ru/goenglish/goenglish/ScheduleServiceImplTest.java#L45

As a result, all the tests, will be executed in one thread, and in the application, it will be executed on different executors

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