-
-
Notifications
You must be signed in to change notification settings - Fork 2
Add tests for app update notifications #231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,6 @@ | |
| import android.net.Uri; | ||
| import android.os.Build; | ||
|
|
||
| import androidx.annotation.RequiresApi; | ||
| import androidx.core.app.NotificationCompat; | ||
|
|
||
| import com.d4rk.androidtutorials.java.R; | ||
|
|
@@ -19,6 +18,8 @@ | |
| import com.google.android.play.core.install.model.AppUpdateType; | ||
| import com.google.android.play.core.install.model.UpdateAvailability; | ||
|
|
||
| import java.util.function.IntSupplier; | ||
|
|
||
| /** | ||
| * Utility class for managing app update notifications. | ||
| * | ||
|
|
@@ -28,11 +29,17 @@ | |
| public class AppUpdateNotificationsManager { | ||
|
|
||
| private final Context context; | ||
| private final IntSupplier sdkIntSupplier; | ||
| private final String updateChannelId = "update_channel"; | ||
| private final int updateNotificationId = 0; | ||
|
|
||
| public AppUpdateNotificationsManager(Context context) { | ||
| this(context, () -> Build.VERSION.SDK_INT); | ||
| } | ||
|
|
||
| AppUpdateNotificationsManager(Context context, IntSupplier sdkIntSupplier) { | ||
| this.context = context; | ||
| this.sdkIntSupplier = sdkIntSupplier; | ||
|
Comment on lines
31
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] Avoid API‐24 java.util.function dependency in manager constructor The new Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| /** | ||
|
|
@@ -43,7 +50,6 @@ public AppUpdateNotificationsManager(Context context) { | |
| * to update the app via the Play Store. The notification includes a deep link to the app's | ||
| * Play Store listing. | ||
| */ | ||
| @RequiresApi(api = Build.VERSION_CODES.O) | ||
| public void checkAndSendUpdateNotification() { | ||
| NotificationManager notificationManager = | ||
| (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); | ||
|
|
@@ -53,12 +59,14 @@ public void checkAndSendUpdateNotification() { | |
| appUpdateInfo -> { | ||
| if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE && | ||
| appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) { | ||
| NotificationChannel updateChannel = new NotificationChannel( | ||
| updateChannelId, | ||
| context.getString(R.string.update_notifications), | ||
| NotificationManager.IMPORTANCE_HIGH | ||
| ); | ||
| notificationManager.createNotificationChannel(updateChannel); | ||
| if (sdkIntSupplier.getAsInt() >= Build.VERSION_CODES.O) { | ||
| NotificationChannel updateChannel = new NotificationChannel( | ||
| updateChannelId, | ||
| context.getString(R.string.update_notifications), | ||
| NotificationManager.IMPORTANCE_HIGH | ||
| ); | ||
| notificationManager.createNotificationChannel(updateChannel); | ||
| } | ||
| NotificationCompat.Builder updateBuilder = new NotificationCompat.Builder(context, updateChannelId) | ||
| .setSmallIcon(R.drawable.ic_notification_update) | ||
| .setContentTitle(context.getString(R.string.notification_update_title)) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| package com.d4rk.androidtutorials.java.notifications.managers; | ||
|
|
||
| import static org.junit.Assert.assertEquals; | ||
| import static org.junit.Assert.assertNotNull; | ||
| import static org.junit.Assert.assertTrue; | ||
| import static org.mockito.ArgumentMatchers.any; | ||
| import static org.mockito.ArgumentMatchers.anyBoolean; | ||
| import static org.mockito.ArgumentMatchers.anyInt; | ||
| import static org.mockito.ArgumentMatchers.anyString; | ||
| import static org.mockito.ArgumentMatchers.eq; | ||
| import static org.mockito.ArgumentMatchers.same; | ||
| import static org.mockito.Mockito.mock; | ||
| import static org.mockito.Mockito.never; | ||
| import static org.mockito.Mockito.verify; | ||
| import static org.mockito.Mockito.when; | ||
|
|
||
| import android.app.Notification; | ||
| import android.app.NotificationChannel; | ||
| import android.app.NotificationManager; | ||
| import android.app.PendingIntent; | ||
| import android.content.Context; | ||
| import android.content.Intent; | ||
| import android.net.Uri; | ||
| import android.os.Build; | ||
|
|
||
| import androidx.core.app.NotificationCompat; | ||
|
|
||
| import com.d4rk.androidtutorials.java.R; | ||
| import com.google.android.gms.tasks.Tasks; | ||
| import com.google.android.play.core.appupdate.AppUpdateInfo; | ||
| import com.google.android.play.core.appupdate.AppUpdateManager; | ||
| import com.google.android.play.core.appupdate.AppUpdateManagerFactory; | ||
| import com.google.android.play.core.install.model.AppUpdateType; | ||
| import com.google.android.play.core.install.model.UpdateAvailability; | ||
|
|
||
| import org.junit.Before; | ||
| import org.junit.Test; | ||
| import org.mockito.MockedConstruction; | ||
| import org.mockito.MockedStatic; | ||
| import org.mockito.Mockito; | ||
|
|
||
| import java.util.List; | ||
| import java.util.concurrent.atomic.AtomicReference; | ||
|
|
||
| public class AppUpdateNotificationsManagerTest { | ||
|
|
||
| private static final String PACKAGE_NAME = "com.d4rk.androidtutorials.java"; | ||
|
|
||
| private Context context; | ||
| private NotificationManager notificationManager; | ||
| private AppUpdateManager appUpdateManager; | ||
| private AppUpdateInfo appUpdateInfo; | ||
|
|
||
| @Before | ||
| public void setUp() { | ||
| context = mock(Context.class); | ||
| notificationManager = mock(NotificationManager.class); | ||
| when(context.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(notificationManager); | ||
| when(context.getString(R.string.update_notifications)).thenReturn("Update notifications"); | ||
| when(context.getString(R.string.notification_update_title)).thenReturn("Update available"); | ||
| when(context.getString(R.string.summary_notification_update)).thenReturn("An update is ready"); | ||
| when(context.getPackageName()).thenReturn(PACKAGE_NAME); | ||
|
|
||
| appUpdateManager = mock(AppUpdateManager.class); | ||
| appUpdateInfo = mock(AppUpdateInfo.class); | ||
| } | ||
|
|
||
| @Test | ||
| public void checkAndSendUpdateNotification_whenFlexibleUpdateAvailable_sendsPlayStoreNotification() { | ||
| when(appUpdateInfo.updateAvailability()).thenReturn(UpdateAvailability.UPDATE_AVAILABLE); | ||
| when(appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)).thenReturn(true); | ||
| when(appUpdateManager.getAppUpdateInfo()).thenReturn(Tasks.forResult(appUpdateInfo)); | ||
|
|
||
| PendingIntent pendingIntent = mock(PendingIntent.class); | ||
| Notification notification = new Notification(); | ||
| AtomicReference<Intent> capturedIntent = new AtomicReference<>(); | ||
|
|
||
| try (MockedStatic<AppUpdateManagerFactory> updateManagerFactory = Mockito.mockStatic(AppUpdateManagerFactory.class); | ||
| MockedStatic<PendingIntent> pendingIntentMock = Mockito.mockStatic(PendingIntent.class); | ||
| MockedConstruction<NotificationCompat.Builder> builderConstruction = mockNotificationBuilder(notification)) { | ||
|
|
||
| updateManagerFactory.when(() -> AppUpdateManagerFactory.create(context)).thenReturn(appUpdateManager); | ||
|
|
||
| pendingIntentMock | ||
| .when(() -> PendingIntent.getActivity(eq(context), eq(0), any(Intent.class), eq(PendingIntent.FLAG_IMMUTABLE))) | ||
| .thenAnswer(invocation -> { | ||
| Intent intent = invocation.getArgument(2); | ||
| capturedIntent.set(intent); | ||
| return pendingIntent; | ||
| }); | ||
|
|
||
| AppUpdateNotificationsManager manager = new AppUpdateNotificationsManager(context, () -> Build.VERSION_CODES.O); | ||
| manager.checkAndSendUpdateNotification(); | ||
|
|
||
| List<NotificationCompat.Builder> builders = builderConstruction.constructed(); | ||
| assertEquals(1, builders.size()); | ||
| NotificationCompat.Builder builder = builders.get(0); | ||
| verify(builder).setContentIntent(pendingIntent); | ||
|
|
||
| verify(notificationManager).createNotificationChannel(any(NotificationChannel.class)); | ||
| verify(notificationManager).notify(eq(0), same(notification)); | ||
|
|
||
| Intent launchIntent = capturedIntent.get(); | ||
| assertNotNull(launchIntent); | ||
| assertEquals(Intent.ACTION_VIEW, launchIntent.getAction()); | ||
| assertEquals(Uri.parse("market://details?id=" + PACKAGE_NAME), launchIntent.getData()); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void checkAndSendUpdateNotification_whenUpdateUnavailable_doesNotSendNotification() { | ||
| when(appUpdateInfo.updateAvailability()).thenReturn(UpdateAvailability.UPDATE_NOT_AVAILABLE); | ||
| when(appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)).thenReturn(false); | ||
| when(appUpdateManager.getAppUpdateInfo()).thenReturn(Tasks.forResult(appUpdateInfo)); | ||
|
|
||
| Notification notification = new Notification(); | ||
|
|
||
| try (MockedStatic<AppUpdateManagerFactory> updateManagerFactory = Mockito.mockStatic(AppUpdateManagerFactory.class); | ||
| MockedStatic<PendingIntent> pendingIntentMock = Mockito.mockStatic(PendingIntent.class); | ||
| MockedConstruction<NotificationCompat.Builder> builderConstruction = mockNotificationBuilder(notification)) { | ||
|
|
||
| updateManagerFactory.when(() -> AppUpdateManagerFactory.create(context)).thenReturn(appUpdateManager); | ||
|
|
||
| AppUpdateNotificationsManager manager = new AppUpdateNotificationsManager(context, () -> Build.VERSION_CODES.O); | ||
| manager.checkAndSendUpdateNotification(); | ||
|
|
||
| verify(notificationManager, never()).createNotificationChannel(any(NotificationChannel.class)); | ||
| verify(notificationManager, never()).notify(anyInt(), any(Notification.class)); | ||
| assertTrue(builderConstruction.constructed().isEmpty()); | ||
| pendingIntentMock.verifyNoInteractions(); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void checkAndSendUpdateNotification_createsChannelOnlyOnOreoOrHigher() { | ||
| when(appUpdateInfo.updateAvailability()).thenReturn(UpdateAvailability.UPDATE_AVAILABLE); | ||
| when(appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)).thenReturn(true); | ||
| when(appUpdateManager.getAppUpdateInfo()).thenReturn(Tasks.forResult(appUpdateInfo)); | ||
|
|
||
| PendingIntent pendingIntent = mock(PendingIntent.class); | ||
| Notification notification = new Notification(); | ||
|
|
||
| try (MockedStatic<AppUpdateManagerFactory> updateManagerFactory = Mockito.mockStatic(AppUpdateManagerFactory.class); | ||
| MockedStatic<PendingIntent> pendingIntentMock = Mockito.mockStatic(PendingIntent.class); | ||
| MockedConstruction<NotificationCompat.Builder> builderConstruction = mockNotificationBuilder(notification)) { | ||
|
|
||
| updateManagerFactory.when(() -> AppUpdateManagerFactory.create(context)).thenReturn(appUpdateManager); | ||
| pendingIntentMock.when(() -> PendingIntent.getActivity(eq(context), eq(0), any(Intent.class), eq(PendingIntent.FLAG_IMMUTABLE))) | ||
| .thenReturn(pendingIntent); | ||
|
|
||
| AppUpdateNotificationsManager manager = new AppUpdateNotificationsManager(context, () -> Build.VERSION_CODES.N_MR1); | ||
| manager.checkAndSendUpdateNotification(); | ||
|
|
||
| verify(notificationManager, never()).createNotificationChannel(any(NotificationChannel.class)); | ||
| verify(notificationManager).notify(eq(0), same(notification)); | ||
|
|
||
| List<NotificationCompat.Builder> builders = builderConstruction.constructed(); | ||
| assertEquals(1, builders.size()); | ||
| verify(builders.get(0)).setContentIntent(pendingIntent); | ||
| } | ||
| } | ||
|
|
||
| private MockedConstruction<NotificationCompat.Builder> mockNotificationBuilder(Notification notification) { | ||
| return Mockito.mockConstruction(NotificationCompat.Builder.class, (mockBuilder, context) -> { | ||
| when(mockBuilder.setSmallIcon(anyInt())).thenReturn(mockBuilder); | ||
| when(mockBuilder.setContentTitle(anyString())).thenReturn(mockBuilder); | ||
| when(mockBuilder.setContentText(anyString())).thenReturn(mockBuilder); | ||
| when(mockBuilder.setAutoCancel(anyBoolean())).thenReturn(mockBuilder); | ||
| when(mockBuilder.setContentIntent(any(PendingIntent.class))).thenReturn(mockBuilder); | ||
| when(mockBuilder.build()).thenReturn(notification); | ||
| }); | ||
| } | ||
| } |