rekire rekire - 5 months ago 50
Android Question

Unit testing: NoSuchMethodError while calling Notification.setLatestEventInfo()


Feel free to improve the title I'm a little uncreative in this special case.


I am implementing a unit test for checking notifications, this is hardly possible I know, but I want to check how far I can automatism it.

I cannot test this simple line of code:

Notification test = new NotificationCompat.Builder(context).build();


The reason is stupid and simple in one. This code here will been executed internally:

public Notification build(Builder b, BuilderExtender extender) {
Notification result = b.mNotification;
result.setLatestEventInfo(b.mContext, b.mContentTitle,
b.mContentText, b.mContentIntent);
[...]


I'm getting this exception:

Caused by: java.lang.NoSuchMethodError: android.app.Notification.setLatestEventInfo(Landroid/content/Context;Ljava/lang/CharSequence;Ljava/lang/CharSequence;Landroid/app/PendingIntent;)V
at android.support.v4.app.NotificationCompat$NotificationCompatImplBase.build(NotificationCompat.java:479)
at android.support.v4.app.NotificationCompat$Builder.build(NotificationCompat.java:1561)


It is not hard to guess that the Google guys call here a method which was removed (or more properly annotated with
@hide
) in the Android Marshmallow SDK. I have verified it that call is missing the the newest documentation, but it was introduced in API 1 AFIK.

How can I work around that?



Things I tried and got stuck:


  • Overriding the callback, and mock that method without invoking that call:

    I got it managed to get that
    Class<?>
    with the callback, but with which method I can override a hole method? I mean I need to patch the call it I cannot just mock it.

  • Injecting that call, but how? I can just override it and not adding it.

  • Suppressing the call with:

    PowerMockito.spy(Notification.class);
    PowerMockito.suppress(PowerMockito.method(Notification.class, "setLatestEventInfo", Context.class, CharSequence.class, CharSequence.class, PendingIntent.class));


    Does not work ether since I try to kick a non existing method.

  • Change the target SDK for this test, but how can I do it?


Answer

The solution is easier than expected. I missed that by default Build.VERSION.SDK_INT has the value 0 since it cannot read the real value. So that support library calls it on just that platforms where this method exists.

With the help of this answer. I just had to add this code:

setFinalStatic(Build.VERSION.class.getDeclaredField("SDK_INT"), 23);

And my the codes works.
Well it still crashes somewhere else, but the notification is created. Wohoo!

And the actual function:

public static void setFinalStatic(Field field, Object newValue) throws IllegalAccessException, NoSuchFieldException
{
    field.setAccessible(true);
    // remove final modifier from field
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
    field.set(null, newValue);
}
Comments