2016-10-27 8 views
0

私はMVPパターンを使用してAndroidアプリケーションを作成しています。 私はRetrofit 2とRxJavaを使用しています。アプリケーションは正常に動作しますRxJava + Retrofit 2ユニットテスト奇妙なエラー

しかし、ユニットテストで私は奇妙なエラーが発生しています。同じテストコードが時々通過し、時には失敗します。

public class AlbumPresenterImpl implements AlbumPresenter { 

private AlbumView view; 
private MediaType type; 
private List<Album> albums; 
private MediaService mediaService; 

public AlbumPresenterImpl(AlbumView view, MediaService mediaService, MediaType type) { 
    this.view = view; 
    this.mediaService = mediaService; 
    this.type = type; 
} 

@Override 
public void getAlbums() { 
    Observable<List<Album>> observable; 

    if (type.equals(MediaType.VIDEO)) { 
     observable = mediaService.getVideoAlbums(); 
    } else { 
     observable = mediaService.getPhotoAlbums(); 
    } 

    observable.doOnSubscribe(view::showProgress) 
      .doAfterTerminate(view::hideProgress) 
      .subscribeOn(Schedulers.io()) 
      .observeOn(AndroidSchedulers.mainThread()) 
      .subscribe(items -> { 
       albums = items; 
       view.showAlbums(albums); 
      }, throwable -> { 
       view.showError(throwable.getLocalizedMessage()); 
      }); 
} 

@Override 
public void onResume() { 
    if (albums == null) { 
     getAlbums(); 
    } 
} 

@Override 
public void onDestroy() { 

} 
} 

をテストしている

エラーこのメッセージを表示して

Wanted but not invoked: 
albumView.showProgress(); 
-> at kz.afckairat.kairat.media.AlbumPresenterTest.checkGetPhotoAlbums(AlbumPresenterTest.java:66) 
Actually, there were zero interactions with this mock. 

Testクラス

public class AlbumPresenterTest { 

enter code here 
private MediaService mediaService; 

private AlbumView albumView; 

private AlbumPresenterImpl photoAlbumPresenter; 

@Before 
public void setUp() throws Exception { 

    albumView = mock(AlbumView.class); 
    mediaService = mock(MediaService.class); 

    photoAlbumPresenter = new AlbumPresenterImpl(albumView, mediaService, MediaType.PHOTO); 

    RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() { 
     @Override 
     public Scheduler getMainThreadScheduler() { 
      return Schedulers.immediate(); 
     } 
    }); 
} 

@After 
public void tearDown() { 
    RxAndroidPlugins.getInstance().reset(); 
} 

@Test 
public void checkGetPhotoAlbums() { 
    List<Album> albums = getAlbumList(); 
    when(mediaService.getPhotoAlbums()).thenReturn(Observable.just(albums)); 

    photoAlbumPresenter.getAlbums(); 

    verify(albumView).showProgress(); 
    verify(albumView).showAlbums(albums); 
    verify(albumView).hideProgress(); 

} 

@Test 
public void checkGetPhotoAlbumError() { 
    String msg = "Error"; 
    when(mediaService.getPhotoAlbums()).thenReturn(Observable.error(new IOException(msg))); 

    photoAlbumPresenter.getAlbums(); 

    verify(albumView).showProgress(); 
    verify(albumView).showError(msg); 
    verify(albumView).hideProgress(); 
} 


private List<Album> getAlbumList() { 
    List<Album> albums = new ArrayList<>(); 
    Album album = new Album(1, "Test1", "test1.jpg", "01.01.2016", 2); 
    albums.add(album); 
    album = new Album(2, "Test2", "test2.jpg", "01.01.2016", 2); 
    albums.add(album); 
    return albums; 
} 
} 

プレゼンタークラスがなぜ時々テストは合格しませんか?

ありがとうございます!

=================================

更新

@Fredが書いたように問題はGithubのa linkから

コードを撮影したスケジューラ

public class RxSchedulersOverrideRule implements TestRule { 

private final RxJavaSchedulersHook mRxJavaSchedulersHook = new RxJavaSchedulersHook() { 
    @Override 
    public Scheduler getIOScheduler() { 
     return Schedulers.immediate(); 
    } 

    @Override 
    public Scheduler getNewThreadScheduler() { 
     return Schedulers.immediate(); 
    } 
}; 

private final RxAndroidSchedulersHook mRxAndroidSchedulersHook = new RxAndroidSchedulersHook() { 
    @Override 
    public Scheduler getMainThreadScheduler() { 
     return Schedulers.immediate(); 
    } 
}; 

// Hack to get around RxJavaPlugins.reset() not being public 
// See https://github.com/ReactiveX/RxJava/issues/2297 
// Hopefully the method will be public in new releases of RxAndroid and we can remove the hack. 
private void callResetViaReflectionIn(RxJavaPlugins rxJavaPlugins) 
     throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { 
    Method method = rxJavaPlugins.getClass().getDeclaredMethod("reset"); 
    method.setAccessible(true); 
    method.invoke(rxJavaPlugins); 
} 

@Override 
public Statement apply(final Statement base, Description description) { 
    return new Statement() { 
     @Override 
     public void evaluate() throws Throwable { 
      RxAndroidPlugins.getInstance().reset(); 
      RxAndroidPlugins.getInstance().registerSchedulersHook(mRxAndroidSchedulersHook); 
      callResetViaReflectionIn(RxJavaPlugins.getInstance()); 
      RxJavaPlugins.getInstance().registerSchedulersHook(mRxJavaSchedulersHook); 

      base.evaluate(); 

      RxAndroidPlugins.getInstance().reset(); 
      callResetViaReflectionIn(RxJavaPlugins.getInstance()); 
     } 
    }; 
} 

}にありました!

とテストクラスの

@Rule 
public final RxSchedulersOverrideRule mOverrideSchedulersRule = new RxSchedulersOverrideRule(); 
+0

メインスレッドのスケジュールをオーバーライドして即座に返すようなサウンドはありますが、あなたは '' Schedulers.io() ''をオーバーライドしませんでした。だから、もし私がこの権利を考えているならば、テストは1つのスレッドで実行され、プレゼンターのメンバーは両方とも別のメンバーになり、結果としてあなたは予想どおりの競争条件を生み出します。あなたはテストのために試してみることができます。ちょうど "即時"のスケジューラをどこでも使うことができます。 – Fred

+0

ありがとう!あなたは正しかった。スケジューラに問題がありました – kamadi

+0

他の人も同様に恩恵を受けることができるように、より完全な回答を書いていきます。 – Fred

答えて

1

あなたがして、メインスレッドスケジューラを上書きするようだ:

RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() { 
    @Override 
    public Scheduler getMainThreadScheduler() { 
     return Schedulers.immediate(); 
    } 
}); 

しかしコードから、観測はまだSchedulers.io()スケジューラ上で実行します。

observable.doOnSubscribe(view::showProgress) 
     .doAfterTerminate(view::hideProgress) 
     .subscribeOn(Schedulers.io()) 
     // ... 

わかるように、即時スケジューラは現在のtでコードを実行しますあなたがioスケジューラにジャンプしてからだと思います。これは、テストが実行されているスケジューラとは異なるものです。

これはテストを1つのスレッドで行い、サブスクライバ/オブザーバブルを別のスレッドで実行します。これは、テストが合格しないことがあり、時には合格しないことがある理由を説明します。競争状態があります。

エッセンシャル最も簡単な方法は、テスト時に、あなたは、すなわち、Schedulers.io()AndroidSchedulers.mainThread()を正しいものを持っているSchedulers.immediate()にし、実行時に両方observeOnsubscribeOnを持っていることを確認することです。

スケジューラをコンストラクタとして渡すことによってこれを行うことができます。また、composeを使用してスケジューラトランスフォーマを作成する方法についてDan Lewが説明しているthisを参照することもできます。次に、実行時にクラスが適切なスケジューラー・トランスを使用していることを確認し、テスト時にすべてのものを即時スレッドに置くトランスを使用することができます。