使用 Espresso 测试 RxJava2 并在 suscribeOn 时出现空指针异常

2024-01-09

Android Studio 3.0 Beta2

我正在测试使用 RxJava2 获取端点列表。该应用程序在正常运行时工作正常。但是,当我使用 espresso 进行测试时,当我尝试执行以下操作时,会出现空指针异常:subscribeOn(scheduler)。对于调度程序我使用trampoline()对彼此而言subscribeOn and 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

为了使用 espresso 测试 RxJava2,我应该做些什么与subscribeOn and 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;
    }
}

对于浓缩咖啡测试,我正在执行以下操作:

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

非常感谢您的任何建议,


您的代码库中存在许多问题。但首先也是最重要的是:你正在以某种方式实例化新的真实对象(而不是模拟),这就是你得到 NPE 的原因,与此无关subscribeOn().

  1. 您应该使测试组件从生产组件扩展。目前是not.

    public interface TestRecipeListComponent extends RecipeListComponent {...}
    
  2. 在您的测试应用程序类中,您正在混合回调,即您正在创建TestRecipeListComponent within createApplicationComponent回调,但你还有另一个回调来执行此操作:createRecipeListComponent().

  3. 你应该not模拟你的每一个和所有的东西MockRecipeListModule。只是模拟出您真正需要模拟的组件。例如,如果你嘲笑RecipeAdapter,那么你为什么期望回收器视图在屏幕上绘制任何东西呢?您只需要模拟数据源提供程序,在您的情况下是RecipeApi。除此之外,不应模拟任何内容,这不是单元测试,这是仪器测试。

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

我已经解决了所有这些问题。我已经到了你写的断言不通过的地步。您应该不厌其烦地继续这样做,因为它与您正在使用的逻辑/架构有关。

我已打开拉取请求here https://github.com/steve1rm/BusbyBaking/pull/27/files.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 Espresso 测试 RxJava2 并在 suscribeOn 时出现空指针异常 的相关文章

  • Numpy:查找两个 3-D 数组之间的欧几里德距离

    给定两个维度为 2 2 2 的 3 D 数组 A 0 0 92 92 0 92 0 92 B 0 0 92 0 0 92 92 92 如何有效地找到 A 和 B 中每个向量的欧几里得距离 我尝试过 for 循环 但速度很慢 而且我正在按 g
  • 用 C# 解析和查询 SOAP

    我正在尝试解析一个大量命名空间的 SOAP 消息 源也可以在here http tinyurl com n3av6k
  • 如何解决 Xcode 7 中的 No Type or Protocol Named 错误?

    我试图passing从第二个开始的值class我正在使用的头等舱protocol and delegate过程 每当我运行我的程序时 我都会遇到以下问题 No Type or Protocol Named locateMeDelegate
  • Liftweb 环境中的后台任务

    我必须编写守护进程 并且我想使用模型来连接到数据库和一些有用的 Lift 类 是否可以运行 Rails 的 rake 任务的模拟 Scala 社区组上也有类似的问题 答案是使用Actors来做后台处理
  • 使用 pytz 获取时区的国家/地区代码?

    我在用着pytz http pytz sourceforge net country information 我已经阅读了整个文档表 但没有看到如何做到这一点 我有一个时区 美国 芝加哥 我想要的只是获取该时区的相应国家 地区代码 美国 它
  • rspec 在需要存根的私有方法中测试私有方法

    Simplecov 检测到我遗漏了一些测试lib api verson rb class class ApiVersion def initialize version version version end def matches req
  • R 脚本 - 如何在错误时继续执行代码

    我编写了一个 R 脚本 其中包含一个检索外部 Web 数据的循环 数据的格式大多数时候是相同的 但有时格式会以不可预测的方式发生变化 并且我的循环崩溃 停止运行 有没有办法不管错误如何继续执行代码 我正在寻找类似于 VBA 中的 On er
  • ASP.NET 中的 ThreadStaticAttribute

    我有一个需要存储的组件static每个线程的值 它是一个通用组件 可以在许多场景中使用 而不仅仅是在 ASP NET 中 我想用 ThreadStatic 属性来实现我的目标 假设它在 ASP NET 场景中也能正常工作 因为我假设每个请求
  • 构建 AOSP 5.1 时出现 API 更改错误

    目前正在尝试构建 android 5 1 0 r5 我已经检查了来源并且没有做任何修改 但是 编译时出现以下错误 Checking API checkpublicapi current out target common obj PACKA
  • sed 将带空格的行插入到特定行

    我在开头有一行空格 例如 Hello world 我想将此行插入到文件中的特定行 例如 将 hello world 插入下一个文件 hello world result hello hello world world 我正在使用这个 sed
  • 在 C++ 中,将 float 转换为 double 再转换回 float 是否给出相同的值

    假设在下面的代码中 float f1 double d1 static cast
  • 致命:Jenkins IIS ID 无效

    我正在尝试设置 Jenkins 从 bitbucket 中提取并构建一个项目 我在 IIS 8 5 Server 2012 r2 上使用它 我已经设置了 Git 和 Bitbucket 插件 我已经建立了一个包含以下内容的项目 Branch
  • 404 路由无法匹配请求的 URL

    我刚刚开始学习zend 框架 questions tagged zend framework并遵循此用户指南 http framework zend com manual 2 3 en index html 我能够成功安装zend skel
  • create() 时不会调用观察者

    我有一个Ember Mixin它观察到它的属性之一 这里bar baz 我扩展了这个 Mixin 并设置了bar baz in the create 参数 但我的观察者没有被调用 这是我的代码 App FooMixin Ember Mixi
  • Google Maps JavaScript API v3 方向功能

    我使用 Google Maps js API v3 我可以根据路径点显示方向this http code google com intl hu apis maps documentation directions Waypoints 我想要
  • MSBuild 找不到 resgen.exe

    我有一台 VM 机器 我在其中复制了 SDK 文件和路径 转到注册表并将密钥添加到注册表中 但我不断收到错误 resgen exe找不到 C Windows Microsoft NET Framework v4 0 30319 Micros
  • JSF 1.2:如何在同一视图上的回发中保持请求范围的托管 bean 处于活动状态?

    是否可以在同一页面上的回发过程中保持请求作用域的 bean 处于活动状态 一般的问题是 bean 在请求结束时被丢弃 并在每次表单提交时重新创建 例如动态操作背后的布尔值disabled readonly and rendered重置为默认
  • 未终止的字符串文字

    我有一个 php 脚本 可以在我的服务器上上传 mp3 文件 我使用 上传 有一个事件 onSelect 文档 http www uploadify com documentation events onselect 当文件上传时调用 使用
  • 访问 django for 循环中的元素

    我有一个 Django 模板 其中包含以下代码 该模板创建多个按钮并尝试通过单击 在同一按钮上 删除 隐藏其中一个按钮 for h in helicopters div class btn group div
  • 在 Python 中窗口“失焦”时读取 HID 输入

    我在一个问题上苦苦挣扎了好几天 但无法让它发挥作用 我刚刚开始使用 python 现在已经面临着我在这个项目中将面临的最大问题 情况是这样的 我必须编写一个扫描条形码的程序 将其传达给在线服务并打印 PDF 这一切都很好 但我也想在窗口 失

随机推荐