使用Espresso测试RxJava2并在suscribeOn时获取空指针异常 [英] Testing RxJava2 using Espresso and getting a null pointer exception when suscribeOn
问题描述
Android Studio 3.0 Beta2
我正在测试使用RxJava2获取端点的列表.正常运行时,该应用程序可以正常运行.但是,当我使用espresso测试时,尝试和subscribeOn(scheduler)
时会出现空指针异常.对于调度程序,我将trampoline()
用于注入的subscribeOn
和observeOn
.
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,我应该做些与subscribeOn
和observeOn
不同的事情吗?
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()
.
-
您应该使测试组件从生产组件扩展.当前不是不是.
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屋!