Search code examples
androidmockitoandroid-espressodagger-2functional-testing

Test instrumentation process crashed when injecting mocked object


I am trying to run a UI test. When I do not mock any injected dependency everything runs well. When I switch module's @Provides return to mocked object, I get the following message:

Started running tests

Test instrumentation process crashed.

Check com.nerfy.features.core.CoreTest#a_shouldDisplayLocationPermissionRequestDialogAtStartup.txt for details.

Tests ran to completion.

I cannot find this txt file anywhere, only some test xmls which are not informative at all.

Edit: The file contains only: INSTRUMENTATION_RESULT: shortMsg=Process crashed. INSTRUMENTATION_CODE: 0.

App works well otherwise.

Application code:

public class Nerfy extends Application {

private AppComponent appComponent;

public static Nerfy get(Context context) {
    return (Nerfy) context.getApplicationContext();
}

@Override
public void onCreate() {
    super.onCreate();
    System.out.println("on create");
    if (appComponent == null) {
        appComponent = buildProdAppComponent();
        appComponent.inject(this);
    }
}

public AppComponent buildProdAppComponent() {
    return DaggerAppComponent.builder()
            .appModule(new AppModule(this))
            .navigationModule(new NavigationModule())
            .refreshModule(new RefreshModule())
            .repoModule(new RepoModule())
            .sharedDataModule(new SharedDataModule())
            .threadsModule(new ThreadsModule())
            .build();
}

//@VisibleForTesting
public void setAppComponent(AppComponent appComponent) {
    System.out.println("on set");

    this.appComponent = appComponent;
    this.appComponent.inject(this);
}

public AppComponent getAppComponent() {
    return appComponent;
}

}

Here is some test code:

@RunWith(AndroidJUnit4.class)
@LargeTest
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class CoreTest {

private UiDevice device;
public SharedData sharedData;
private AppComponent espressoAppComponent;

@Rule
public ActivityTestRule<Core> mActivityRule =
        new ActivityTestRule<Core>(Core.class) {
            @Override
            protected void beforeActivityLaunched() {
                System.out.println("beforeActivityLaunched");
                sharedData = mock(SharedData.class);
                System.out.println("shared data " + sharedData);

                espressoAppComponent = DaggerAppComponent.builder()
                        .appModule(new AppModule((Application) getTargetContext().getApplicationContext()))
                        .navigationModule(new NavigationModule())
                        .refreshModule(new RefreshModule())
                        .repoModule(new RepoModule())
                        .sharedDataModule(new SharedDataModule(){
                            @Override
                            public SharedData providesSharedData(Application application) {
                                return sharedData;
                            }
                        })
                        .threadsModule(new ThreadsModule())
                        .build();
                ((Nerfy) getTargetContext().getApplicationContext()).setAppComponent(espressoAppComponent);
                super.beforeActivityLaunched();
            }
        };

@Before
public void setUp() {
}

@Test
    @SdkSuppress(minSdkVersion = 23)
    public void a_shouldDisplayLocationPermissionRequestDialogAtStartup() throws Exception {
        assertViewWithTextIsVisible(device, TEXT_ALLOW);
        assertViewWithTextIsVisible(device, TEXT_DENY);
        denyCurrentPermission(device);
    }

Nothing gets printed out.

But if I switch mocked shared data to real one

@Override
public SharedData providesSharedData(Application application) {
      return super.providesSharedData(application);

It all runs well, test fails properly because shared data is not mocked. Then I also noticed that application's onCreate is called before test activity rule's beforeActivityLaunched, even though on some tutorials they say it should be called before applications on create.

I have followed tutorials like http://blog.sqisland.com/2015/04/dagger-2-espresso-2-mockito.html

Android Mocking a Dagger2 injected dependency for a Espresso test

How do you override a module/dependency in a unit test with Dagger 2.0?

and similar; also official dagger2 docs.

I have tried subclassing application, creating new model and subclassing component for test only; also not initing activityRule until on setUp() end, after injecting component here instead on overriding of beforeActivityLaunched().

Also tried trivial fixes like rebuilding the project, restarting android studio, updating dependencies.

Mocking some other modules also brings the same result while mocking others works fine.

Any ideas?


Solution

  • I have tested other modules. Found out that mocked Refresher module would crash the test process due to it having Observable field. The tested activity tries to subscribe on it, but it's null.

    The SharedData was much more difficult to figure out - it handles shared preferences, so basically just strings and booleans. I had to check all the usages. The crash happened while trying to put string value as url (not mocked, so null again) to Picasso to get an image.

    Moral of the story - one proper crash message is worth two days of blind debugging.