Тестирование RxJava2 с использованием эспрессо и получение исключения нулевого указателя при suscribeOn

Android Studio 3.0 Beta2

я тестирую получение списка для конечной точки с помощью RxJava2. Приложение отлично работает при нормальной работе. Однако, когда я тестирую использование эспрессо, я получаю исключение нулевого указателя, когда я пытаюсь и subscribeOn(scheduler). Для планировщиков я использую trampoline() для subscribeOn и observeOn, которые вводят.

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 с помощью эспрессо есть ли что-то, что я должен сделать, что отличается для subscribeOn и observeOn?

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

и для теста expresso я делаю следующее:

@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 ответов


существует множество проблем в коде. Но прежде всего следующее: Вы каким-то образом создаете новые реальные объекты (а не насмешки), и поэтому вы получаете NPE, нет ничего общего с subscribeOn().

  1. вы должны сделать ваш компонент теста удлинить от вашего компонента продукции. В настоящее время это не.

    public interface TestRecipeListComponent extends RecipeListComponent {...}
    
  2. в вашем классе тестового приложения вы смешиваете обратные вызовы, т. е. вы создаем TestRecipeListComponent внутри createApplicationComponent обратный вызов, но у вас есть другой обратный вызов для этого:createRecipeListComponent().

  3. вы должны не издеваться над каждым и всем в вашем MockRecipeListModule. Просто издевайтесь над компонентом, который вам действительно нужно издеваться. Например, если вы издеваетесь RecipeAdapter, тогда почему вы ожидаете, что recycler view нарисует что-нибудь на экране? Вам просто нужно издеваться над поставщиком источника данных, который в вашем случае является RecipeApi. Кроме этого ничего не должно быть высмеянный, это не модульный тест, это инструментальный тест.

  4. внутри RecipeListView#onCreate() вы создаете новый RecipeListComponent, тогда как необходимо не, вы должны получить этот компонент от Application класс, потому что вы уже создали он есть. Это влияет на тесты: вы не можете управлять зависимостями оттуда, потому что RecipeListView просто проигнорирует все зависимости, которые вы изменили из тестов, и создаст новый компонент, который обеспечит другие зависимости, таким образом, ваши заглушки не верните данные, которые вы явно жестко закодировали в тесте (на самом деле они даже не будут вызваны, реальные объекты будут). Это именно то, из-за чего вы испытывали проблему.

я все это исправил. Я дошел до точки, где утверждение, которое вы написали, не проходит. Вы должны принять хлопот, чтобы продолжить с этим, потому что это связано с логикой/архитектурой, которую вы используете.

я открыл запрос на вытягивание здесь.