unit test android unit-testing mockito robolectric retrofit

android - unit - retrofit testing



Respuesta falsa observable<T> en pruebas unitarias de Android (3)

Tengo una interfaz API y estoy probando una View que involucra llamadas de red.

@Config(emulateSdk = 18) public class SampleViewTest extends RobolectricTestBase { ServiceApi apiMock; @Inject SampleView fixture; @Override public void setUp() { super.setUp(); //injection is performed in super apiMock = mock(ServiceApi.class); fixture = new SampleView(activity); fixture.setApi(apiMock); } @Test public void testSampleViewCallback() { when(apiMock.requestA()).thenReturn(Observable.from(new ResponseA()); when(apiMock.requestB()).thenReturn(Observable.from(new ResponseB()); AtomicReference<Object> testResult = new AtomicReference<>(); fixture.updateView(new Callback() { @Override public void onSuccess(Object result) { testResult.set(result); } @Override public void onError(Throwable error) { throw new RuntimeException(error); } }); verify(apiMock, times(1)).requestA(); verify(apiMock, times(1)).requestB(); assertNotNull(testResult.get()); } }

Por alguna razón, los métodos de apiMock nunca se llaman y la verificación siempre falla.

En mi opinión estoy llamando a mi api así

apiV2.requestA() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer());

¿Que me estoy perdiendo aqui?

Actualización # 1:
Después de algunas investigaciones, parece que cuando en mi implementación (ejemplo anterior) observeOn(AndroidSchedulers.mainThread()) no se llama. Todavía no sé por qué.

Actualización # 2:
Al suscribirse al igual que apiV2.requestA().subscribe(new Observer()); Todo funciona bien, se llama simulacro de api y pasa la prueba.
Avanzar ShadowLooper.idleMainLooper(5000) no hizo nada. Incluso agarró el looper del manejador en HandlerThreadScheduler y lo adelantó. Mismo resultado

Actualización # 3: Agregar código real donde se usa la API.

public void updateView(final Callback) { Observable.zip(wrapObservable(api.requestA()), wrapObservable(api.requestB()), new Func2<ResponseA, ResponseB, Object>() { @Override public Object call(ResponseA responseA, ResponseB responseB) { return mergeBothResponses(responseA, responseB); } } ).subscribe(new EndlessObserver<Object>() { @Override public void onError(Throwable e) { Log.e(e); listener.onError(e); } @Override public void onNext(Object config) { Log.d("Configuration updated [%s]", config.toString()); listener.onSuccess(config); } }); } protected <T> Observable<T> wrapObservable(Observable<T> observable) { return observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()); }


¿Qué versión de rxjava estás usando? Sé que hubo algunos cambios en la versión 0.18. * Con respecto al ExecutorScheduler. Tuve un problema similar al tuyo cuando usé 0.18.3, donde no obtuve el mensaje onComplete porque mi suscripción se cancelaría antes de tiempo. La única razón por la que le menciono esto es que una solución en 0.19.0 solucionó mi problema.

Desafortunadamente, no puedo explicar los detalles de lo que se solucionó, está más allá de mi comprensión en este punto, pero si resulta ser la misma causa, tal vez alguien con más comprensión podría explicarlo. Aquí está el enlace de lo que estoy hablando https://github.com/Netflix/RxJava/issues/1219 .

Esto no es una gran respuesta, sino más bien un aviso en caso de que pueda ayudarlo.


Como @ champ016 declaró, hubo problemas con las versiones de RxJava que son inferiores a 0.19.0 .

Cuando se usa 0.19.0 el siguiente enfoque funciona. Aunque todavía no entiendo por qué tengo que avanzar AMBOS loopers.

@Test public void testSampleViewCallback() { when(apiMock.requestA()).thenReturn(Observable.from(new ResponseA()); when(apiMock.requestB()).thenReturn(Observable.from(new ResponseB()); AtomicReference<Object> testResult = new AtomicReference<>(); fixture.updateView(new Callback() { @Override public void onSuccess(Object result) { testResult.set(result); } @Override public void onError(Throwable error) { throw new RuntimeException(error); } }); ShadowLooper.idleMainLooper(5000); Robolectric.shadowOf( Reflection.field("handler") .ofType(Handler.class) .in(AndroidSchedulers.mainThread()) .get().getLooper()) .idle(5000); verify(apiMock, times(1)).requestA(); verify(apiMock, times(1)).requestB(); assertNotNull(testResult.get()); }


Sigo pensando en cómo usar rxjava correctamente, pero trataría de modificar su código para que solo pueda observar On (mainThread) en el Observable comprimido final en lugar de hacerlo en los dos Observables de la respuesta de la solicitud original. Luego verificaría si esto afecta el hecho de que tienes que avanzar ambos Loopers o no.

Para simplificar sus pruebas y eliminar la necesidad del ralentí del Looper, eliminaría el hilo de la ecuación, ya que no necesita un procesamiento en segundo plano al ejecutar las pruebas. Puede hacer eso inyectando sus programadores en lugar de crearlos de forma estática. Cuando ejecute su código de producción, tendrá inyectados AndroidSchedulers.mainThread y Schedulers.io, y cuando ejecute el código de prueba, inyectará Schedulers.immediate cuando corresponda.

@Inject @UIScheduler /* Inject Schedulers.immediate for tests and AndroidSchedulers.mainThread for production code */ private Scheduler mainThreadSched; @Inject @IOScheduler /* Inject Scheduler.immediate for tests and Schedulers.io for production code */ private Scheduler ioSched; public void updateView(final Callback) { Observable.zip(wrapObservable(api.requestA()), wrapObservable(api.requestB()), new Func2<ResponseA, ResponseB, Object>() { @Override public Object call(ResponseA responseA, ResponseB responseB) { return mergeBothResponses(responseA, responseB); } } ).observeOn(mainThreadSched) .subscribe(new EndlessObserver<Object>() { @Override public void onError(Throwable e) { Log.e(e); listener.onError(e); } @Override public void onNext(Object config) { Log.d("Configuration updated [%s]", config.toString()); listener.onSuccess(config); } }); } protected <T> Observable<T> wrapObservable(Observable<T> observable) { return observable.subscribeOn(ioSched); }