16

It seems to me that building an Activity unit test with Robolectric's lifecycle utilities (starting with Robolectric.buildActivity()) and spying on the same Activity with a Mockito spy are mutually exclusive.

Because buildActivity() controls the construction of the Activity object, the only place to add a spy for the Activity is after calling buildActivity(). However, the spy doesn't function properly when it's added after the fact.

This is especially true when spying for side effects of ActivityController lifecycle methods such as create(), start() and resume(). I assume this is because the ActivityController holds a reference to the "real" Activity object and not the spy that was added later.

So is there any way to spy an Activity that's being unit tested with Robolectric, such that the spy works properly when calling the lifecycle methods via Robolectric's ActivityController?

tbraun89
  • 2,246
  • 3
  • 25
  • 44
espressoexcess
  • 191
  • 1
  • 3
  • 1
    Why are you spying on your Activity? – Corey D Oct 16 '13 at 20:02
  • 1
    To confirm that certain methods were called with expected arguments in unit tests. Also to disable methods which don't work well in unit testing (i.e. ones that launch other Activities) using `doNothing()`. – espressoexcess Oct 17 '13 at 14:23
  • Robolectric does not actually start an activity when `startActivity` is called. Your `Activity` is the unit under test and should not be spied/mocked. Mocking/spying external units is perfectly valid but you should avoid doing this to your object under test. – Corey D Oct 18 '13 at 05:19
  • 6
    "Your Activity is the unit under test and should not be spied/mocked." Exactly why not? Many of our tests use spies to verify other routines in the class being tested were called as expected. Also, spies are useful for disabling methods in Android classes that have undesirable side effects in unit tests. A classic example is spying `Activity.startActivity()` to verify another `Activity` was launched as the result of a method call, without incurring the side effects of sending the `Intent`. – espressoexcess Oct 21 '13 at 15:41
  • Another case where it makes sense to spy on activities is when the object under test is not an activity, but a fragment that interacts with a dummy activity. E.g. a test can check if the fragment correctly calls back to an attached activity which is passed to the fragment in onAttach. See https://groups.google.com/forum/#!topic/robolectric/doUomaLr83Q – dschulten Jan 09 '14 at 09:34

2 Answers2

4

The answer is using the reflection to replace the "real" Activity object in ActivityController.

@Test
public void someTestMethod() throws NoSuchFieldException, IllegalAccessException {
    ActivityController<LoginActivity> ac = Robolectric.buildActivity(LoginActivity.class);
    LoginActivity spiedActivity = spy(ac.get());

    replaceComponentInActivityController(ac, spiedActivity);

    ac.create();

    // do your work
 }

public static void replaceComponentInActivityController(ActivityController<?> activityController, Activity activity)
        throws NoSuchFieldException, IllegalAccessException {
    Field componentField = ComponentController.class.getDeclaredField("component");
    componentField.setAccessible(true);
    componentField.set(activityController, activity);
}

I test it by Robolectric 3.1, and it's ok.

Riki
  • 2,774
  • 1
  • 24
  • 38
  • Nice this helped me a lot to combine Robolectric with Mockito! For me the crux was also to defer activity creation (the call to controller.create()) to after replacing the component. Also works with Robolectric 4.2. – Christian.D Mar 19 '19 at 14:07
1

At least for the case where the activity is not the object under test, but only a dummy activity which hosts a fragment under test, it is possible to inject a mock into the test activity which can verify interactions with the activity via the communication interface between fragment and activity (following http://developer.android.com/training/basics/fragments/communicating.html).

dschulten
  • 2,994
  • 1
  • 27
  • 44
  • How would you do that for an "dummy" activity that doesn't exist in the manifest? If you're interested in testing a Fragment that expects the activity attaching to implement an interface... looking to have that mocked rather than having to create the real (heavy) activity. – Colin M. Aug 07 '14 at 23:29