testobserver test rxjava observer android unit-testing junit rx-java rx-java2

android - testobserver - test observer rxjava



Prueba de Android RxJava 2 JUnit-getMainLooper en android.os.Looper no se burla de RuntimeException (7)

Estoy encontrando una excepción RuntimeException cuando intento ejecutar pruebas JUnit para un presentador que está utilizando observeOn(AndroidSchedulers.mainThread()) .

Dado que son pruebas de JUnit puras y no pruebas de instrumentación de Android, no tienen acceso a las dependencias de Android, lo que me hace encontrar el siguiente error al ejecutar las pruebas:

java.lang.ExceptionInInitializerError at io.reactivex.android.schedulers.AndroidSchedulers$1.call(AndroidSchedulers.java:35) at io.reactivex.android.schedulers.AndroidSchedulers$1.call(AndroidSchedulers.java:33) at io.reactivex.android.plugins.RxAndroidPlugins.callRequireNonNull(RxAndroidPlugins.java:70) at io.reactivex.android.plugins.RxAndroidPlugins.initMainThreadScheduler(RxAndroidPlugins.java:40) at io.reactivex.android.schedulers.AndroidSchedulers.<clinit>(AndroidSchedulers.java:32) … Caused by: java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked. See http://g.co/androidstudio/not-mocked for details. at android.os.Looper.getMainLooper(Looper.java) at io.reactivex.android.schedulers.AndroidSchedulers$MainHolder.<clinit>(AndroidSchedulers.java:29) ... java.lang.NoClassDefFoundError: Could not initialize class io.reactivex.android.schedulers.AndroidSchedulers at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) …


Acabo de añadir

RxAndroidPlugins.setInitMainThreadSchedulerHandler(scheduler -> Schedulers.trampoline());

en @Before método @Before .


Como en el consejo de este artículo de Medium por Peter Tackage , puede inyectar los Programadores usted mismo.

Todos sabemos que llamar directamente a los métodos estáticos puede hacer que las clases sean difíciles de probar y si utiliza un marco de inyección de dependencia como Dagger 2, la inyección de los Programadores puede ser especialmente fácil. El ejemplo es el siguiente:

Defina una interfaz en su proyecto:

public interface SchedulerProvider { Scheduler ui(); Scheduler computation(); Scheduler io(); Scheduler special(); // Other schedulers as required… }

Definir una implementación:

final class AppSchedulerProvider implements SchedulerProvider { @Override public Scheduler ui() { return AndroidSchedulers.mainThread(); } @Override public Scheduler computation() { return Schedulers.computation(); } @Override public Scheduler io() { return Schedulers.io(); } @Override public Scheduler special() { return MyOwnSchedulers.special(); } }

Ahora, en lugar de usar referencias directas a los programadores como este:

bookstoreModel.getFavoriteBook() .map(Book::getTitle) .delay(5, TimeUnit.SECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe(view::setBookTitle));

Utiliza referencias a su interfaz:

bookstoreModel.getFavoriteBook() .map(Book::getTitle) .delay(5, TimeUnit.SECONDS, this.schedulerProvider.computation()) .observeOn(this.schedulerProvider.ui()) .subscribe(view::setBookTitle));

Ahora para sus pruebas, podría definir un proveedor de pruebas de planificación como este:

public final class TestSchedulersProvider implements SchedulerProvider { @Override public Scheduler ui() { return new TestScheduler(); } @Override public Scheduler io() { return Schedulers.trampoline(); //or test scheduler if you want } //etc }

Ahora tiene todas las ventajas de usar TestScheduler cuando quiera en sus pruebas unitarias. Esto es útil para situaciones en las que es posible que desee probar un retraso:

@Test public void testIntegerOneIsEmittedAt20Seconds() { //arrange TestObserver<Integer> o = delayedRepository.delayedInt() .test(); //act testScheduler.advanceTimeTo(20, TimeUnit.SECONDS); //assert o.assertValue(1); }

De lo contrario, si no desea utilizar los Programadores inyectados, los enlaces estáticos mencionados en los otros métodos se pueden hacer utilizando lambdas:

@Before public void setUp() { RxAndroidPlugins.setInitMainThreadSchedulerHandler(h -> Schedulers.trampoline()); RxJavaPlugins.setIoSchedulerHandler(h -> Schedulers.trampoline()); //etc }


Este error se produce porque el programador predeterminado devuelto por AndroidSchedulers.mainThread() es una instancia de LooperScheduler y depende de las dependencias de Android que no están disponibles en las pruebas de JUnit.

Podemos evitar este problema al inicializar RxAndroidPlugins con un Programador diferente antes de que se ejecuten las pruebas. Puedes hacer esto dentro de un método @BeforeClass así:

@BeforeClass public static void setUpRxSchedulers() { Scheduler immediate = new Scheduler() { @Override public Disposable scheduleDirect(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) { // this prevents Errors when scheduling with a delay return super.scheduleDirect(run, 0, unit); } @Override public Worker createWorker() { return new ExecutorScheduler.ExecutorWorker(Runnable::run); } }; RxJavaPlugins.setInitIoSchedulerHandler(scheduler -> immediate); RxJavaPlugins.setInitComputationSchedulerHandler(scheduler -> immediate); RxJavaPlugins.setInitNewThreadSchedulerHandler(scheduler -> immediate); RxJavaPlugins.setInitSingleSchedulerHandler(scheduler -> immediate); RxAndroidPlugins.setInitMainThreadSchedulerHandler(scheduler -> immediate); }

O puede crear una TestRule personalizada que le permitirá reutilizar la lógica de inicialización en múltiples clases de prueba.

public class RxImmediateSchedulerRule implements TestRule { private Scheduler immediate = new Scheduler() { @Override public Disposable scheduleDirect(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) { // this prevents Errors when scheduling with a delay return super.scheduleDirect(run, 0, unit); } @Override public Worker createWorker() { return new ExecutorScheduler.ExecutorWorker(Runnable::run); } }; @Override public Statement apply(final Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { RxJavaPlugins.setInitIoSchedulerHandler(scheduler -> immediate); RxJavaPlugins.setInitComputationSchedulerHandler(scheduler -> immediate); RxJavaPlugins.setInitNewThreadSchedulerHandler(scheduler -> immediate); RxJavaPlugins.setInitSingleSchedulerHandler(scheduler -> immediate); RxAndroidPlugins.setInitMainThreadSchedulerHandler(scheduler -> immediate); try { base.evaluate(); } finally { RxJavaPlugins.reset(); RxAndroidPlugins.reset(); } } }; } }

Que luego puede aplicar a su clase de prueba

public class TestClass { @ClassRule public static final RxImmediateSchedulerRule schedulers = new RxImmediateSchedulerRule(); @Test public void testStuff_stuffHappens() { ... } }

Ambos métodos asegurarán que los programadores predeterminados se anularán antes de que se ejecute cualquiera de las pruebas y antes de que se acceda a AndroidSchedulers .

Al anular los programadores de RxJava con un programador inmediato para la prueba de unidad también se asegurará de que los usos de RxJava en el código que se está probando se ejecuten de manera sincrónica, lo que facilitará mucho la escritura de las pruebas de unidad.

Fuentes:
https://www.infoq.com/articles/Testing-RxJava2 https://medium.com/@peter.tackage/overriding-rxandroid-schedulers-in-rxjava-2-5561b3d14212


Para RxJava 1 puedes crear diferentes planificadores como este:

@Before public void setUp() throws Exception { // Override RxJava schedulers RxJavaHooks.setOnIOScheduler(new Func1<Scheduler, Scheduler>() { @Override public Scheduler call(Scheduler scheduler) { return Schedulers.immediate(); } }); RxJavaHooks.setOnComputationScheduler(new Func1<Scheduler, Scheduler>() { @Override public Scheduler call(Scheduler scheduler) { return Schedulers.immediate(); } }); RxJavaHooks.setOnNewThreadScheduler(new Func1<Scheduler, Scheduler>() { @Override public Scheduler call(Scheduler scheduler) { return Schedulers.immediate(); } }); // Override RxAndroid schedulers final RxAndroidPlugins rxAndroidPlugins = RxAndroidPlugins.getInstance(); rxAndroidPlugins.registerSchedulersHook(new RxAndroidSchedulersHook() { @Override public Scheduler getMainThreadScheduler() { return Schedulers.immediate(); } }); } @After public void tearDown() throws Exception { RxJavaHooks.reset(); RxAndroidPlugins.getInstance().reset(); }

Unidad de prueba de aplicaciones de Android con actualización y rxjava.


Recibía el mismo error al probar LiveData. Al probar LiveData, se necesita esta InstantTaskExecutorRule además de RxImmediateSchedulerRule si la clase que se está probando tiene un hilo de fondo y LiveData.

@RunWith(MockitoJUnitRunner::class) class MainViewModelTest { companion object { @ClassRule @JvmField val schedulers = RxImmediateSchedulerRule() } @Rule @JvmField val rule = InstantTaskExecutorRule() @Mock lateinit var dataRepository: DataRepository lateinit var model: MainViewModel @Before fun setUp() { model = MainViewModel(dataRepository) } @Test fun fetchData() { //given val returnedItem = createDummyItem() val observer = mock<Observer<List<Post>>>() model.getPosts().observeForever(observer) //when liveData.value = listOf(returnedItem) //than verify(observer).onChanged(listOf(Post(returnedItem.id, returnedItem.title, returnedItem.url))) } }

Referencia: https://pbochenski.pl/blog/07-12-2017-testing_livedata.html


Solo para agregar a la respuesta de starkej2, funcionó muy bien para mí hasta que encontré error al probar un Observable.timer (). No hay ayuda en eso, pero afortunadamente lo conseguí trabajando con la siguiente definición de Programador, con todas las demás pruebas que también pasaron.

new Scheduler() { @Override public Worker createWorker() { return new ExecutorScheduler.ExecutorWorker(new ScheduledThreadPoolExecutor(1) { @Override public void execute(@NonNull Runnable runnable) { runnable.run(); } }); } };

Descansa como en la respuesta de starkej2. Espero que esto ayude a alguien.


Tuve este problema y llegué a esta publicación, pero no pude encontrar nada para RX 1. Así que esta es la solución si tiene el mismo problema en la primera versión.

@BeforeClass public static void setupClass() { RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() { @Override public Scheduler getMainThreadScheduler() { return Schedulers.trampoline(); } }); }