2017-04-11 5 views
16

observeOn(AndroidSchedulers.mainThread())を使用しているプレゼンターに対してJUnitテストを実行しようとすると、RuntimeExceptionが発生します。Android RxJava 2 JUnit test - android.os.LooperのgetMainLooperが模擬されていないRuntimeException

彼らは純粋なJUnitテストではなくAndroidの計装試験であるので、彼らはテストを実行するときに、私は次のエラーが発生する原因、Androidの依存関係へのアクセスを持っていない:

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) 
    … 

答えて

32

ので、このエラーが発生しますAndroidSchedulers.mainThread()によって返されるデフォルトスケジューラはLooperSchedulerのインスタンスであり、JUnitテストでは利用できないAndroidの依存関係に依存しています。

テストを実行する前に別のスケジューラでRxAndroidPluginsを初期化することで、この問題を回避できます。

@BeforeClass 
public static void setUpRxSchedulers() { 
    Scheduler immediate = new Scheduler() { 
     @Override 
     public Disposable scheduleDirect(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) { 
      // this prevents StackOverflowErrors 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); 
} 

をそれとも、あなたが複数のテストクラス全体で初期化ロジックを再利用することができますカスタムTestRuleを作成することができます:あなたはとても似@BeforeClass方法のこの内部を行うことができます。

あなたは、あなたのテストクラスに適用することができます
public class RxImmediateSchedulerRule implements TestRule { 
    private Scheduler immediate = new Scheduler() { 
     @Override 
     public Disposable scheduleDirect(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) { 
      // this prevents StackOverflowErrors 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(); 
       } 
      } 
     }; 
    } 
} 

public class TestClass { 
    @ClassRule public static final RxImmediateSchedulerRule schedulers = new RxImmediateSchedulerRule(); 

    @Test 
    public void testStuff_stuffHappens() { 
     ... 
    } 
} 

どちらの方法でも、デフォルトのスケジューラは、テストのいずれかを実行する前に上書きされますとAndroidSchedulersにアクセスする前にいることを確認します。

単体テストの即時スケジューラでRxJavaスケジューラをオーバーライドすることで、テスト対象のコード内のRxJavaの使用が同期して実行されるようになります。単体テストの記述がはるかに簡単になります。あなたはこのような別のスケジューラを作成することができますRxJava 1については
https://www.infoq.com/articles/Testing-RxJava2 https://medium.com/@peter.tackage/overriding-rxandroid-schedulers-in-rxjava-2-5561b3d14212

+1

それは魔法のように動作します:) –

+0

plsはまた、あなたがにStackOverflowError –

+0

更新に実行する場合、必要になることがあります小さな変更のために私の答えをチェック: あなたはスケジューラを設定するには、RxJavaHooksメソッドを使用することができます。 はまた、あなたは利用可能TestSchedulerを持っているだけでなく、単にSchedulers.immediate()私はRxJavaHooksがRxJava 2で除去し、それが機能は今RxJavaPlugins –

1

ソースだけstarkej2の答えに追加する

@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(); 
} 

Unit testing android application with retrofit and rxjava

1

を、それがために非常によく働きました私はObservable.timer()をテストするときにstackoverflowerrorを調べました。幸いなことに私はスケジューラの定義と一緒に働いていますが、他のすべてのテストも通過しています。

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

starkej2の回答にあるように安心してください。これが誰かを助けることを望みます。

+1

おかげに組み込まれています信じて – starkej2

7

私はちょうど@Before annoted方法で

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

を追加しました。

0

this Medium article by Peter Tackageのアドバイスで、スケジューラを自分で注入することができます。

静的メソッドを直接呼び出すことはテストが難しいクラスに対して行うことができ、Dagger 2のような依存性注入フレームワークを使用すると、スケジューラを注入するのが特に簡単になることがあります。

プロジェクト内のインタフェースを定義します:代わりに次のようにスケジューラへの直接参照を使用しての今

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(); 
    } 
} 

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

は実装を定義

次のように例があります
bookstoreModel.getFavoriteBook() 
       .map(Book::getTitle) 
       .delay(5, TimeUnit.SECONDS) 
       .observeOn(AndroidSchedulers.mainThread()) 
       .subscribe(view::setBookTitle)); 

あなたのインターフェースへの参照は、

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

今、あなたのテストのために、あなたはこのようなTestSchedulersProviderを定義することができます。

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 
} 

あなたのユニットテスト中にしたいときは、今TestSchedulerを使用する利点のすべてを持っています。

:あなたが注入されたスケジューラ他の方法で述べた静的なフックがラムダを使用して行うことができますを使用したくない場合は、そうでない場合

@Test 
public void testIntegerOneIsEmittedAt20Seconds() { 
    //arrange 
    TestObserver<Integer> o = delayedRepository.delayedInt() 
      .test(); 

    //act 
    testScheduler.advanceTimeTo(20, TimeUnit.SECONDS); 

    //assert 
    o.assertValue(1); 
} 

:これは、あなたが遅延をテストする可能性がある状況に重宝します

@Before 
public void setUp() { 
    RxAndroidPlugins.setInitMainThreadSchedulerHandler(h -> Schedulers.trampoline()); 
    RxJavaPlugins.setIoSchedulerHandler(h -> Schedulers.trampoline()); 
//etc 
} 
関連する問題