javaandroidtestingandroid-espressoconductor

Start specific conductor controller in (Espresso) Android instrumentation test


I'm writing Espresso tests for an app written using Conductor. I'd like to specify which controller to start for each test, so that I don't need to get Espresso to click through the app from the beginning Activity for each. Since there is only one Activity and not much on SO or google about Conductor the closest I could find was this question? Or is this not possible?

I've tried making the router static and adding a getter in an attempt to set a specific root for testing with no success.

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

In MainActivity:

public static Router getRouter() {
    return router;
}

In Instrumentation Test:

@Rule
public ActivityTestRule<MainActivity> testRule = new ActivityTestRule<>(MainActivity.class);

@Before
public void setUp() {
    Router router = testRule.getActivity().getRouter();
    router.setRoot(RouterTransaction.with(new ControllerIwantToTest()));
}

@Test
public void titleIsDisplayed() {
    onView(withText("My Controllers Title")).check(matches(isDisplayed()));
}

Solution

  • If anyone else comes across the same issue, I've solved the problem by doing the following:

    @RunWith(AndroidJUnit4.class)
    public class NoBundleControllerEspressoTest {
    
    private Router router;
    
    @Rule
    public ActivityTestRule<NoBundleConductorActivity> testRule = new ActivityTestRule<>(NoBundleConductorActivity.class);
    
    @Before
    public void setUp() {
        Activity activity = testRule.getActivity();
        activity.runOnUiThread(() -> {
                   router = testRule.getActivity().getRouter();
                   router.setRoot(RouterTransaction.with(new NoBundleController()));
               });
    }
    
    @Test
    public void titleIsDisplayed() {
        onView(withText("Super Awesome Title")).check(matches(isDisplayed()));
        }
    }
    

    Or if your controller uses a Bundle in it's contructor as most of our do:

    @RunWith(AndroidJUnit4.class)
    public class BundleControllerEspressoTest {
    
    private Router router;
    private ControllerBundleData controllerData;
    
    @Rule
    public ActivityTestRule<BundleConductorActivity> testRule = new ActivityTestRule<>(BundleConductorActivity.class);
    
    @Before
    public void setUp() {
        controllerData = new ControllerBundleData();
        Bundle bundle = new Bundle();
        bundle.putSerializable(YOUR_BUNDLE_KEY, controllerData);
    
        Activity activity = testRule.getActivity();
        activity.runOnUiThread(() -> {
                   router = testRule.getActivity().getRouter();
                   router.setRoot(RouterTransaction.with(new BundleController(bundle)));
               });
    }
    
    @Test
    public void titleIsDisplayed() {
        onView(withText("Super Awesome Title")).check(matches(isDisplayed()));
        }
    }