使用Espresso测试RxJava2并在suscribeOn时获取空指针异常 [英] Testing RxJava2 using Espresso and getting a null pointer exception when suscribeOn

本文介绍了使用Espresso测试RxJava2并在suscribeOn时获取空指针异常的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Android Studio 3.0 Beta2

我正在测试使用RxJava2获取端点的列表.正常运行时,该应用程序可以正常运行.但是,当我使用espresso测试时,尝试和subscribeOn(scheduler)时会出现空指针异常.对于调度程序,我将trampoline()用于注入的subscribeOnobserveOn.

I am testing getting a list for an endpoint using RxJava2. The app works fine when running normally. However, when I test using espresso I get a null pointer exception when I try and subscribeOn(scheduler). For the schedulers I use the trampoline() for both subscribeOn and observeOn which are injected.

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

对于使用espresso测试RxJava2,我应该做些与subscribeOnobserveOn不同的事情吗?

For testing RxJava2 using espresso is there anything I should do that is different for the subscribeOn and observeOn?

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

这是我正在接受测试的课程

This is my class under 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();
        }
    }
}

调度程序的界面在这里,为了进行测试,我正在使用注射的蹦床

The interface for the schedulers is here and for testing I am using trampoline which is injected

@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的模拟模块

Mock Module for 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;
    }
}

对于表情测试,我正在执行以下操作:

And for the expresso test I am doing the following:

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

非常感谢您的任何建议,

Many thanks for any suggestions,

推荐答案

您的代码库中存在许多问题.但是首先要注意的是:您正在以某种方式实例化新的真实对象(而不是模拟对象),这就是为什么要获得NPE的原因,与subscribeOn()无关.

There are numerous of problems in your codebase. But first and foremost is following: you are somehow instantiating new real objects (not mocks) and that's why you are getting NPE, there is nothing to do with subscribeOn().

  1. 您应该使测试组件从生产组件扩展.当前不是不是.

public interface TestRecipeListComponent extends RecipeListComponent {...}

  • 在测试应用程序类中,您混合使用了回调,即在createApplicationComponent回调中创建了TestRecipeListComponent,但是您还可以使用另一个回调:createRecipeListComponent().

  • In your test application class you are mixing callbacks, i.e. you are creating TestRecipeListComponent within createApplicationComponent callback, but you have another callback for doing that: createRecipeListComponent().

    您应该模拟出MockRecipeListModule中的所有内容.只是模拟出您真正需要模拟的组件.例如,如果模拟RecipeAdapter,那么您如何期望回收者视图在屏幕上绘制任何内容?您只需要模拟数据源提供程序,在您的情况下为RecipeApi.除了没有什么可以模仿的,这不是单元测试,这是仪器测试.

    You should not mock out each an everything in your MockRecipeListModule. Just mock out component, that you really need to mock out. For example, if you mock RecipeAdapter, then how come you expect recycler view to draw anything on the screen? You just need to mock out data source provider, which in your case is RecipeApi. Other than that nothing should be mocked out, this is not a unit test, this is instrumentation test.

    RecipeListView#onCreate()之内,您正在创建一个新的RecipeListComponent,而您应该,您应该从Application类中获取该组件,因为您已经在其中创建了该组件. .这会影响测试:您无法从那里控制依赖项,因为RecipeListView只会忽略您从测试中更改的所有依赖项,并且会创建一个新组件来提供其他依赖项,因此存根将返回您已在测试中明确进行硬编码的数据(实际上,它们甚至都不会被调用,实际对象会被调用).这正是您遇到此问题的原因.

    Within RecipeListView#onCreate() you are creating a new RecipeListComponent, whereas you should not, you should get that component from the Application class, because you have already created it there. This affect on the tests: you cannot control dependencies from there, because RecipeListView would just ignore all the dependencies you have changed from tests and will create a new component which will provide other dependencies, thus your stubs would not return the data that you have explicitly hard coded in test (in fact they won't be even called, real objects would be). This is exactly what you were experiencing the issue from.

    我已经解决了所有这些问题.我到了你写的断言没有通过的地步.您应该继续进行此操作,因为它与您正在使用的逻辑/体系结构有关.

    I've fixed all of this. I've come to a point where the assertion you wrote does not pass. You should take the hassle to continue with this, because it is connected with the logics/architecture you are using.

    我已经在此处打开了拉取请求.

    I've opened a pull request here.

    这篇关于使用Espresso测试RxJava2并在suscribeOn时获取空指针异常的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆