Skip to content

Commit 66d9058

Browse files
Add tests for app update notifications
1 parent 31e7acb commit 66d9058

File tree

2 files changed

+189
-8
lines changed

2 files changed

+189
-8
lines changed

app/src/main/java/com/d4rk/androidtutorials/java/notifications/managers/AppUpdateNotificationsManager.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import android.net.Uri;
99
import android.os.Build;
1010

11-
import androidx.annotation.RequiresApi;
1211
import androidx.core.app.NotificationCompat;
1312

1413
import com.d4rk.androidtutorials.java.R;
@@ -19,6 +18,8 @@
1918
import com.google.android.play.core.install.model.AppUpdateType;
2019
import com.google.android.play.core.install.model.UpdateAvailability;
2120

21+
import java.util.function.IntSupplier;
22+
2223
/**
2324
* Utility class for managing app update notifications.
2425
*
@@ -28,11 +29,17 @@
2829
public class AppUpdateNotificationsManager {
2930

3031
private final Context context;
32+
private final IntSupplier sdkIntSupplier;
3133
private final String updateChannelId = "update_channel";
3234
private final int updateNotificationId = 0;
3335

3436
public AppUpdateNotificationsManager(Context context) {
37+
this(context, () -> Build.VERSION.SDK_INT);
38+
}
39+
40+
AppUpdateNotificationsManager(Context context, IntSupplier sdkIntSupplier) {
3541
this.context = context;
42+
this.sdkIntSupplier = sdkIntSupplier;
3643
}
3744

3845
/**
@@ -43,7 +50,6 @@ public AppUpdateNotificationsManager(Context context) {
4350
* to update the app via the Play Store. The notification includes a deep link to the app's
4451
* Play Store listing.
4552
*/
46-
@RequiresApi(api = Build.VERSION_CODES.O)
4753
public void checkAndSendUpdateNotification() {
4854
NotificationManager notificationManager =
4955
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -53,12 +59,14 @@ public void checkAndSendUpdateNotification() {
5359
appUpdateInfo -> {
5460
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE &&
5561
appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
56-
NotificationChannel updateChannel = new NotificationChannel(
57-
updateChannelId,
58-
context.getString(R.string.update_notifications),
59-
NotificationManager.IMPORTANCE_HIGH
60-
);
61-
notificationManager.createNotificationChannel(updateChannel);
62+
if (sdkIntSupplier.getAsInt() >= Build.VERSION_CODES.O) {
63+
NotificationChannel updateChannel = new NotificationChannel(
64+
updateChannelId,
65+
context.getString(R.string.update_notifications),
66+
NotificationManager.IMPORTANCE_HIGH
67+
);
68+
notificationManager.createNotificationChannel(updateChannel);
69+
}
6270
NotificationCompat.Builder updateBuilder = new NotificationCompat.Builder(context, updateChannelId)
6371
.setSmallIcon(R.drawable.ic_notification_update)
6472
.setContentTitle(context.getString(R.string.notification_update_title))
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package com.d4rk.androidtutorials.java.notifications.managers;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertNotNull;
5+
import static org.junit.Assert.assertTrue;
6+
import static org.mockito.ArgumentMatchers.any;
7+
import static org.mockito.ArgumentMatchers.anyBoolean;
8+
import static org.mockito.ArgumentMatchers.anyInt;
9+
import static org.mockito.ArgumentMatchers.anyString;
10+
import static org.mockito.ArgumentMatchers.eq;
11+
import static org.mockito.ArgumentMatchers.same;
12+
import static org.mockito.Mockito.mock;
13+
import static org.mockito.Mockito.never;
14+
import static org.mockito.Mockito.verify;
15+
import static org.mockito.Mockito.when;
16+
17+
import android.app.Notification;
18+
import android.app.NotificationChannel;
19+
import android.app.NotificationManager;
20+
import android.app.PendingIntent;
21+
import android.content.Context;
22+
import android.content.Intent;
23+
import android.net.Uri;
24+
import android.os.Build;
25+
26+
import androidx.core.app.NotificationCompat;
27+
28+
import com.d4rk.androidtutorials.java.R;
29+
import com.google.android.gms.tasks.Tasks;
30+
import com.google.android.play.core.appupdate.AppUpdateInfo;
31+
import com.google.android.play.core.appupdate.AppUpdateManager;
32+
import com.google.android.play.core.appupdate.AppUpdateManagerFactory;
33+
import com.google.android.play.core.install.model.AppUpdateType;
34+
import com.google.android.play.core.install.model.UpdateAvailability;
35+
36+
import org.junit.Before;
37+
import org.junit.Test;
38+
import org.mockito.MockedConstruction;
39+
import org.mockito.MockedStatic;
40+
import org.mockito.Mockito;
41+
42+
import java.util.List;
43+
import java.util.concurrent.atomic.AtomicReference;
44+
45+
public class AppUpdateNotificationsManagerTest {
46+
47+
private static final String PACKAGE_NAME = "com.d4rk.androidtutorials.java";
48+
49+
private Context context;
50+
private NotificationManager notificationManager;
51+
private AppUpdateManager appUpdateManager;
52+
private AppUpdateInfo appUpdateInfo;
53+
54+
@Before
55+
public void setUp() {
56+
context = mock(Context.class);
57+
notificationManager = mock(NotificationManager.class);
58+
when(context.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(notificationManager);
59+
when(context.getString(R.string.update_notifications)).thenReturn("Update notifications");
60+
when(context.getString(R.string.notification_update_title)).thenReturn("Update available");
61+
when(context.getString(R.string.summary_notification_update)).thenReturn("An update is ready");
62+
when(context.getPackageName()).thenReturn(PACKAGE_NAME);
63+
64+
appUpdateManager = mock(AppUpdateManager.class);
65+
appUpdateInfo = mock(AppUpdateInfo.class);
66+
}
67+
68+
@Test
69+
public void checkAndSendUpdateNotification_whenFlexibleUpdateAvailable_sendsPlayStoreNotification() {
70+
when(appUpdateInfo.updateAvailability()).thenReturn(UpdateAvailability.UPDATE_AVAILABLE);
71+
when(appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)).thenReturn(true);
72+
when(appUpdateManager.getAppUpdateInfo()).thenReturn(Tasks.forResult(appUpdateInfo));
73+
74+
PendingIntent pendingIntent = mock(PendingIntent.class);
75+
Notification notification = new Notification();
76+
AtomicReference<Intent> capturedIntent = new AtomicReference<>();
77+
78+
try (MockedStatic<AppUpdateManagerFactory> updateManagerFactory = Mockito.mockStatic(AppUpdateManagerFactory.class);
79+
MockedStatic<PendingIntent> pendingIntentMock = Mockito.mockStatic(PendingIntent.class);
80+
MockedConstruction<NotificationCompat.Builder> builderConstruction = mockNotificationBuilder(notification)) {
81+
82+
updateManagerFactory.when(() -> AppUpdateManagerFactory.create(context)).thenReturn(appUpdateManager);
83+
84+
pendingIntentMock
85+
.when(() -> PendingIntent.getActivity(eq(context), eq(0), any(Intent.class), eq(PendingIntent.FLAG_IMMUTABLE)))
86+
.thenAnswer(invocation -> {
87+
Intent intent = invocation.getArgument(2);
88+
capturedIntent.set(intent);
89+
return pendingIntent;
90+
});
91+
92+
AppUpdateNotificationsManager manager = new AppUpdateNotificationsManager(context, () -> Build.VERSION_CODES.O);
93+
manager.checkAndSendUpdateNotification();
94+
95+
List<NotificationCompat.Builder> builders = builderConstruction.constructed();
96+
assertEquals(1, builders.size());
97+
NotificationCompat.Builder builder = builders.get(0);
98+
verify(builder).setContentIntent(pendingIntent);
99+
100+
verify(notificationManager).createNotificationChannel(any(NotificationChannel.class));
101+
verify(notificationManager).notify(eq(0), same(notification));
102+
103+
Intent launchIntent = capturedIntent.get();
104+
assertNotNull(launchIntent);
105+
assertEquals(Intent.ACTION_VIEW, launchIntent.getAction());
106+
assertEquals(Uri.parse("market://details?id=" + PACKAGE_NAME), launchIntent.getData());
107+
}
108+
}
109+
110+
@Test
111+
public void checkAndSendUpdateNotification_whenUpdateUnavailable_doesNotSendNotification() {
112+
when(appUpdateInfo.updateAvailability()).thenReturn(UpdateAvailability.UPDATE_NOT_AVAILABLE);
113+
when(appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)).thenReturn(false);
114+
when(appUpdateManager.getAppUpdateInfo()).thenReturn(Tasks.forResult(appUpdateInfo));
115+
116+
Notification notification = new Notification();
117+
118+
try (MockedStatic<AppUpdateManagerFactory> updateManagerFactory = Mockito.mockStatic(AppUpdateManagerFactory.class);
119+
MockedStatic<PendingIntent> pendingIntentMock = Mockito.mockStatic(PendingIntent.class);
120+
MockedConstruction<NotificationCompat.Builder> builderConstruction = mockNotificationBuilder(notification)) {
121+
122+
updateManagerFactory.when(() -> AppUpdateManagerFactory.create(context)).thenReturn(appUpdateManager);
123+
124+
AppUpdateNotificationsManager manager = new AppUpdateNotificationsManager(context, () -> Build.VERSION_CODES.O);
125+
manager.checkAndSendUpdateNotification();
126+
127+
verify(notificationManager, never()).createNotificationChannel(any(NotificationChannel.class));
128+
verify(notificationManager, never()).notify(anyInt(), any(Notification.class));
129+
assertTrue(builderConstruction.constructed().isEmpty());
130+
pendingIntentMock.verifyNoInteractions();
131+
}
132+
}
133+
134+
@Test
135+
public void checkAndSendUpdateNotification_createsChannelOnlyOnOreoOrHigher() {
136+
when(appUpdateInfo.updateAvailability()).thenReturn(UpdateAvailability.UPDATE_AVAILABLE);
137+
when(appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)).thenReturn(true);
138+
when(appUpdateManager.getAppUpdateInfo()).thenReturn(Tasks.forResult(appUpdateInfo));
139+
140+
PendingIntent pendingIntent = mock(PendingIntent.class);
141+
Notification notification = new Notification();
142+
143+
try (MockedStatic<AppUpdateManagerFactory> updateManagerFactory = Mockito.mockStatic(AppUpdateManagerFactory.class);
144+
MockedStatic<PendingIntent> pendingIntentMock = Mockito.mockStatic(PendingIntent.class);
145+
MockedConstruction<NotificationCompat.Builder> builderConstruction = mockNotificationBuilder(notification)) {
146+
147+
updateManagerFactory.when(() -> AppUpdateManagerFactory.create(context)).thenReturn(appUpdateManager);
148+
pendingIntentMock.when(() -> PendingIntent.getActivity(eq(context), eq(0), any(Intent.class), eq(PendingIntent.FLAG_IMMUTABLE)))
149+
.thenReturn(pendingIntent);
150+
151+
AppUpdateNotificationsManager manager = new AppUpdateNotificationsManager(context, () -> Build.VERSION_CODES.N_MR1);
152+
manager.checkAndSendUpdateNotification();
153+
154+
verify(notificationManager, never()).createNotificationChannel(any(NotificationChannel.class));
155+
verify(notificationManager).notify(eq(0), same(notification));
156+
157+
List<NotificationCompat.Builder> builders = builderConstruction.constructed();
158+
assertEquals(1, builders.size());
159+
verify(builders.get(0)).setContentIntent(pendingIntent);
160+
}
161+
}
162+
163+
private MockedConstruction<NotificationCompat.Builder> mockNotificationBuilder(Notification notification) {
164+
return Mockito.mockConstruction(NotificationCompat.Builder.class, (mockBuilder, context) -> {
165+
when(mockBuilder.setSmallIcon(anyInt())).thenReturn(mockBuilder);
166+
when(mockBuilder.setContentTitle(anyString())).thenReturn(mockBuilder);
167+
when(mockBuilder.setContentText(anyString())).thenReturn(mockBuilder);
168+
when(mockBuilder.setAutoCancel(anyBoolean())).thenReturn(mockBuilder);
169+
when(mockBuilder.setContentIntent(any(PendingIntent.class))).thenReturn(mockBuilder);
170+
when(mockBuilder.build()).thenReturn(notification);
171+
});
172+
}
173+
}

0 commit comments

Comments
 (0)