Ich teste gerade das Abrufen einer Liste für einen Endpunkt mit RxJava2. Die App funktioniert einwandfrei, wenn sie normal läuft. Wenn ich jedoch einen Espresso benutze, bekomme ich eine Nullzeiger-Ausnahme, wenn ich es versuche und subscribeOn(scheduler)
. Für die Scheduler benutze ich die trampoline()
sowohl für als auch observeOn
, die injiziert werden.
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
Zum Testen RxJava2 Espresso verwendet, ist es etwas, was ich, dass andere für die subscribeOn
und observeOn
tun sollte?
@Singleton
@Component(modules = {
MockNetworkModule.class,
MockAndroidModule.class,
MockExoPlayerModule.class
})
public interface TestBusbyBakingComponent extends BusbyBakingComponent {
TestRecipeListComponent add(MockRecipeListModule mockRecipeListModule);
}
Das ist meine Klasse im Test
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();
}
}
}
Die Schnittstelle für die Disponenten hier ist und zum Testen ich Trampolin bin mit dem
injiziert wird@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();
}
};
}
}
Mock-Modul für RecipleAPI
@Module
public class MockNetworkModule {
@Singleton
@Provides
public RecipesAPI providesRecipeAPI() {
return Mockito.mock(RecipesAPI.class);
}
}
Dies ist 210, wie die Komponenten
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;
}
}
erstellt und für den Expresso Test, den ich tue folgende:
@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"))));
}
}
Stapelüberwachung:
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)
Vielen Dank für Ihre Anregungen,
Können Sie den Stack-Trace der Ausnahme, die Sie erhalten, veröffentlichen? Wo wird MockRecipeSchedulersModule auch injiziert? Ich sehe nur MockRecipeListModule, das injiziert wird. – jdonmoyer
@jdonmoyer Entschuldigung, ich habe das falsche Modul eingefügt. Es heißt MockAndroidModule, das die Provider enthält, die die Scheduler zurückgeben (Ich habe meine Frage aktualisiert). Die Art, wie sie injiziert werden, benutzt die Konstruktorinjektion. Inject public RecipeListModelImp (NonNull RecipesAPI rezepteAPI, NonNull RecipeSchedulers recipeScheduler). Ich kann keinen Stack-Trace bereitstellen, bis ich später nach Hause komme. Danke – ant2009
Wo bieten Sie Test-Implementierung von RecipesAPI? Ich kann nur sehen, dass es in RecipeListViewAndroidTest injiziert wird und vom 'Mock' Typ ist, richtig? Außerdem sehe ich nicht, wo 'MockAndroidModule' zu' TestBusbyBakingComponent' hinzugefügt wird. Könnten Sie das bitte näher ausführen? –