Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly spy on an Activity

Mockito creates a proxy instance when some thing is spied on. Now, is there any way to forward setters that are then executed on that proxy instance to the real instance that sits behind it?

Rationale: I have an object instance that I do not have completely under my control, i.e. an Android activity. I can give most parts of my app the proxied version and that runs fine as is, but because I need to create the spy / proxy very early during the creation phase of the activity, it is not yet fully instantiated, e.g. the base context is not attached. This happens on the proxy instance and is of course not used by the activity instance itself (which refers to itself via Activity.this). The end result is that this leads to all kinds of crashes because resource resolving happens via this base context, so the internal Fragment machinery throws NPEs and more.

Here is some code:

public class CustomAndroidJUnitRunner extends AndroidJUnitRunner {
    @Override
    public Activity newActivity(ClassLoader cl, String className, Intent intent)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        Activity activity = super.newActivity(cl, className, intent);
        return maybeStubSomeDelegate(activity);
    }

    private Activity maybeStubSomeDelegate(Activity activity) {
        if (!(activity instanceof SomeDelegate)) {
            return activity;
        }
        Activity spiedActivity = spy(activity);
        doReturn(SomeDelegateMock.getInstance())
            .when((SomeDelegate) spiedActivity)
            .getDelegate();
        return spiedActivity;
    }
}

I'm clueless - any ideas?

like image 929
Thomas Keller Avatar asked Feb 19 '16 00:02

Thomas Keller


2 Answers

Using:

android Test Support library's SingleActivityFactory, ActivityTestRule and Mockito's spy()

dependencies {
  androidTestImplementation 'com.android.support.test:runner:1.0.2'
  androidTestImplementation 'com.android.support.test:rules:1.0.2'
  androidTestImplementation 'org.mockito:mockito-android:2.21.0'
}

Outline:

inject the spied instance inside SingleActivityFactory's implementation

Code:

public class MainActivityTest {
    MainActivity subject;

    SingleActivityFactory<MainActivity> activityFactory = new SingleActivityFactory<MainActivity>(MainActivity.class) {
        @Override
        protected MainActivity create(Intent intent) {
            subject = spy(getActivityClassToIntercept());
            return subject;
        }
    };

    @Rule
    public ActivityTestRule<MainActivity> testRule = new ActivityTestRule<>(activityFactory, true, true);

    @Test
    public void activity_isBeingSpied() {
        verify(subject).setContentView(R.layout.activity_main);
    }

}
like image 166
dm6801 Avatar answered Oct 17 '22 17:10

dm6801


You can use Robolectric to create your own proxy (or as Robolectric calls them "Shadows") to your activity,

When you create the proxy you can create your own setters that can trigger the real object methods,

How to create a shadow example:

@Implements(Bitmap.class)
public class MyShadowBitmap {

@RealObject private Bitmap realBitmap;
private int bitmapQuality = -1;

@Implementation
public boolean compress(Bitmap.CompressFormat format, int quality, OutputStream stream) {
  bitmapQuality = quality;
  return realBitmap.compress(format, quality, stream);
}

public int getQuality() {
  return bitmapQuality;
}
}
}

when the @RealObject is your real instance,

To use this shadow using Robolectric test runner define a new test class as follows:

@RunWith(RobolectricTestRunner.class)
@Config(shadows = MyShadowBitmap.class)
public class MyTestClass {}

To pull the current shadow instance use the method:

shadowOf()

And in any case, here is s link to Robolectric:

http://robolectric.org/custom-shadows/

like image 27
leon karabchesvky Avatar answered Oct 17 '22 18:10

leon karabchesvky