8
Android Studio 3.0 Beta2 

我正在測試使用RxJava2獲取端點的列表。該應用程序正常運行時工作正常。但是,當我使用espresso進行測試時,我嘗試使用subscribeOn(scheduler)時收到空指針異常。對於調度程序,我使用trampoline()來注入subscribeOnobserveOn使用Espresso測試RxJava2並在suscribeOn時得到空指針異常

Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'io.reactivex.Observable io.reactivex.Observable.subscribeOn(io.reactivex.Scheduler)' on a null object reference 

對於使用咖啡測試RxJava2有什麼我應該做的是爲subscribeOnobserveOn不同?

@Singleton 
@Component(modules = { 
     MockNetworkModule.class, 
     MockAndroidModule.class, 
     MockExoPlayerModule.class 
}) 
public interface TestBusbyBakingComponent extends BusbyBakingComponent { 
    TestRecipeListComponent add(MockRecipeListModule mockRecipeListModule); 
} 

這是我在測試

public class RecipeListModelImp 
     implements RecipeListModelContract { 

    private RecipesAPI recipesAPI; 
    private RecipeSchedulers recipeSchedulers; 
    private CompositeDisposable compositeDisposable = new CompositeDisposable(); 

    @Inject 
    public RecipeListModelImp(@NonNull RecipesAPI recipesAPI, @NonNull RecipeSchedulers recipeSchedulers) { 
     this.recipesAPI = Preconditions.checkNotNull(recipesAPI); 
     this.recipeSchedulers = Preconditions.checkNotNull(recipeSchedulers); 
    } 

    @Override 
    public void getRecipesFromAPI(final RecipeGetAllListener recipeGetAllListener) { 
     compositeDisposable.add(recipesAPI.getAllRecipes() 
       .subscribeOn(recipeSchedulers.getBackgroundScheduler()) /* NULLPOINTER EXCEPTION HERE */ 
       .observeOn(recipeSchedulers.getUIScheduler()) 
       .subscribeWith(new DisposableObserver<List<Recipe>>() { 
        @Override 
        protected void onStart() {} 

        @Override 
        public void onNext(@io.reactivex.annotations.NonNull List<Recipe> recipeList) { 
         recipeGetAllListener.onRecipeGetAllSuccess(recipeList); 
        } 

        @Override 
        public void onError(Throwable e) { 
         recipeGetAllListener.onRecipeGetAllFailure(e.getMessage()); 
        } 

        @Override 
        public void onComplete() {} 
       })); 
    } 

    @Override 
    public void releaseResources() { 
     if(compositeDisposable != null && !compositeDisposable.isDisposed()) { 
      compositeDisposable.clear(); 
      compositeDisposable.dispose(); 
     } 
    } 
} 

的調度接口是這裏測試我使用的蹦牀將其注入

@Module 
public class MockAndroidModule { 
    @Singleton 
    @Provides 
    Context providesContext() { 
     return Mockito.mock(Context.class); 
    } 

    @Singleton 
    @Provides 
    Resources providesResources() { 
     return Mockito.mock(Resources.class); 
    } 

    @Singleton 
    @Provides 
    SharedPreferences providesSharedPreferences() { 
     return Mockito.mock(SharedPreferences.class); 
    } 

    @Singleton 
    @Provides 
    RecipeSchedulers provideRecipeSchedulers() { 
     return new RecipeSchedulers() { 
      @Override 
      public Scheduler getBackgroundScheduler() { 
       return Schedulers.trampoline(); 
      } 

      @Override 
      public Scheduler getUIScheduler() { 
       return Schedulers.trampoline(); 
      } 
     }; 
    } 
} 

模擬模塊RecipleAPI

@Module 
public class MockNetworkModule { 
    @Singleton 
    @Provides 
    public RecipesAPI providesRecipeAPI() { 
     return Mockito.mock(RecipesAPI.class); 
    } 
} 

這是怎樣的組件創建

public class TestBusbyBakingApplication extends BusbyBakingApplication { 
    private TestBusbyBakingComponent testBusbyBakingComponent; 
    private TestRecipeListComponent testRecipeListComponent; 

    @Override 
    public TestBusbyBakingComponent createApplicationComponent() { 
     testBusbyBakingComponent = createTestBusbyBakingComponent(); 
     testRecipeListComponent = createTestRecipeListComponent(); 

     return testBusbyBakingComponent; 
    } 

    private TestBusbyBakingComponent createTestBusbyBakingComponent() { 
     testBusbyBakingComponent = DaggerTestBusbyBakingComponent.builder() 
       .build(); 

     return testBusbyBakingComponent; 
    } 

    private TestRecipeListComponent createTestRecipeListComponent() { 
     testRecipeListComponent = testBusbyBakingComponent.add(new MockRecipeListModule()); 
     return testRecipeListComponent; 
    } 
} 

而對於快報測試我做了以下內容:

@RunWith(MockitoJUnitRunner.class) 
public class RecipeListViewAndroidTest { 
    @Inject RecipesAPI recipesAPI; 

    @Mock RecipeListModelContract.RecipeGetAllListener mockRecipeListener; 

    @Rule 
    public ActivityTestRule<MainActivity> mainActivity = 
      new ActivityTestRule<>(
        MainActivity.class, 
        true, 
        false); 

    @Before 
    public void setup() throws Exception { 
     Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); 
     BusbyBakingApplication busbyBakingApplication = 
       (BusbyBakingApplication)instrumentation.getTargetContext().getApplicationContext(); 

     TestBusbyBakingComponent component = (TestBusbyBakingComponent)busbyBakingApplication.createApplicationComponent(); 
     component.add(new MockRecipeListModule()).inject(this); 
    } 

    @Test 
    public void shouldReturnAListOfRecipes() throws Exception { 
     List<Recipe> recipeList = new ArrayList<>(); 
     Recipe recipe = new Recipe(); 
     recipe.setName("Test Brownies"); 
     recipe.setServings(10); 
     recipeList.add(recipe); 

     when(recipesAPI.getAllRecipes()).thenReturn(Observable.just(recipeList)); 
     doNothing().when(mockRecipeListener).onRecipeGetAllSuccess(recipeList); 

     mainActivity.launchActivity(new Intent()); 

     onView(withId(R.id.rvRecipeList)).check(matches(hasDescendant(withText("Test Brownies")))); 
    } 
} 

堆棧跟蹤:

at me.androidbox.busbybaking.recipieslist.RecipeListModelImp.getRecipesFromAPI(RecipeListModelImp.java:37) 
at me.androidbox.busbybaking.recipieslist.RecipeListPresenterImp.retrieveAllRecipes(RecipeListPresenterImp.java:32) 
at me.androidbox.busbybaking.recipieslist.RecipeListView.getAllRecipes(RecipeListView.java:99) 
at me.androidbox.busbybaking.recipieslist.RecipeListView.onCreateView(RecipeListView.java:80) 
at android.support.v4.app.Fragment.performCreateView(Fragment.java:2192) 
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1299) 
at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1528) 
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1595) 
at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:758) 
at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2363) 
at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2149) 
at android.support.v4.app.FragmentManagerImpl.optimizeAndExecuteOps(FragmentManager.java:2103) 
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2013) 
at android.support.v4.app.FragmentController.execPendingActions(FragmentController.java:388) 
at android.support.v4.app.FragmentActivity.onStart(FragmentActivity.java:607) 
at android.support.v7.app.AppCompatActivity.onStart(AppCompatActivity.java:178) 
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1237) 
at android.support.test.runner.MonitoringInstrumentation.callActivityOnStart(MonitoringInstrumentation.java:544) 
at android.app.Activity.performStart(Activity.java:6268) 

非常感謝您的任何建議,

+1

你可以發佈你得到的異常的堆棧跟蹤嗎?另外,MockRecipeSchedulersModule在哪裏注入?我只看到MockRecipeListModule被注入。 – jdonmoyer

+0

@jdonmoyer對不起,我包含了錯誤的模塊。它被稱爲MockAndroidModule,其中包含返回調度程序的提供程序(我更新了我的問題)。他們注入的方式是使用構造函數注入。注入 public RecipeListModelImp(NonNull RecipesAPI recipesAPI,NonNull RecipeSchedulers recipeSchedulers)。直到今天晚些時候我纔回家,我無法提供堆棧跟蹤。謝謝 – ant2009

+1

你在哪裏提供RecipesAPI的測試實現?我只能看到,它被注入RecipeListViewAndroidTest並且是'Mock'類型的嗎?此外,我沒有看到,「MockAndroidModule」被添加到「TestBusbyBakingComponent」中。你能詳細解釋一下嗎? –

回答

6

你的代碼庫有很多問題。但首先是以下幾點:你以某種方式實例化新的真實對象(而不是嘲笑),這就是爲什麼你得到NPE,與subscribeOn()沒有任何關係。

  1. 您應該使您的測試組件從您的生產組件擴展。目前它是而不是

    public interface TestRecipeListComponent extends RecipeListComponent {...} 
    
  2. 在您的測試應用程序類你混合回調,即你createApplicationComponent回調中創建TestRecipeListComponent,但你有另一個回調做這件事:createRecipeListComponent()

  3. 你應該不是嘲笑你的每一個在你的MockRecipeListModule的一切。只是嘲笑組件,你真的需要嘲笑。例如,如果您模擬RecipeAdapter,那麼您如何期望回收者視圖在屏幕上繪製任何內容?你只需要嘲笑數據源提供者,在你的情況下是RecipeApi。除了沒有什麼應該被嘲笑,這不是一個單元測試,這是儀器測試。

  4. RecipeListView#onCreate()要創建一個新的RecipeListComponent,而你應該,你應該從Application類成分,因爲你已經創建它。這對測試產生了影響:因爲RecipeListView只會忽略所有您已經從測試中更改的依賴關係,並且會創建一個新的組件來提供其他依賴關係,因此您的存根將會而不是返回您的數據在測試中明確硬編碼(實際上它們甚至不會被調用,實際的對象會是)。這正是你遇到問題的原因。

我修復了所有這些問題。我已經到了你寫的斷言沒有通過的地步。您應該繼續這樣做,因爲它與您正在使用的邏輯/體系結構相關。

我已經打開了拉請求here

+0

偉大的工作。我檢查了代碼,一切正常。我不得不在TestBusbyBakingApplication中做一些小的重構,並且在這裏創建了一個PR:https://github.com/azizbekian/BusbyBaking/pull/1。但是,這回答了我的問題。萬分感謝。 – ant2009