Skip to content

Commit f6a32c3

Browse files
Add tests for notification scheduling and worker thresholds
1 parent 3fe6eaa commit f6a32c3

File tree

3 files changed

+259
-0
lines changed

3 files changed

+259
-0
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.d4rk.androidtutorials.java.notifications.managers;
2+
3+
import android.app.AlarmManager;
4+
import android.app.PendingIntent;
5+
import android.content.Context;
6+
import android.content.Intent;
7+
8+
import com.d4rk.androidtutorials.java.notifications.receivers.AppUsageNotificationReceiver;
9+
10+
import org.junit.Test;
11+
import org.mockito.ArgumentCaptor;
12+
import org.mockito.MockedStatic;
13+
14+
import java.util.concurrent.TimeUnit;
15+
import java.util.concurrent.atomic.AtomicReference;
16+
17+
import static org.junit.Assert.assertEquals;
18+
import static org.junit.Assert.assertNotNull;
19+
import static org.junit.Assert.assertTrue;
20+
import static org.mockito.ArgumentMatchers.any;
21+
import static org.mockito.ArgumentMatchers.anyInt;
22+
import static org.mockito.ArgumentMatchers.eq;
23+
import static org.mockito.Mockito.mock;
24+
import static org.mockito.Mockito.mockStatic;
25+
import static org.mockito.Mockito.verify;
26+
import static org.mockito.Mockito.when;
27+
28+
public class AppUsageNotificationsManagerTest {
29+
30+
@Test
31+
public void scheduleAppUsageCheck_setsRepeatingAlarmForReceiver() {
32+
Context context = mock(Context.class);
33+
AlarmManager alarmManager = mock(AlarmManager.class);
34+
when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn(alarmManager);
35+
36+
PendingIntent pendingIntent = mock(PendingIntent.class);
37+
AtomicReference<Intent> capturedIntent = new AtomicReference<>();
38+
39+
try (MockedStatic<PendingIntent> mockedPendingIntent = mockStatic(PendingIntent.class)) {
40+
mockedPendingIntent.when(() -> PendingIntent.getBroadcast(
41+
eq(context),
42+
eq(0),
43+
any(Intent.class),
44+
eq(PendingIntent.FLAG_IMMUTABLE)
45+
)).thenAnswer(invocation -> {
46+
Intent intent = invocation.getArgument(2);
47+
capturedIntent.set(intent);
48+
return pendingIntent;
49+
});
50+
51+
long beforeCall = System.currentTimeMillis();
52+
AppUsageNotificationsManager manager = new AppUsageNotificationsManager(context);
53+
manager.scheduleAppUsageCheck();
54+
long afterCall = System.currentTimeMillis();
55+
56+
ArgumentCaptor<Long> triggerCaptor = ArgumentCaptor.forClass(Long.class);
57+
verify(alarmManager).setRepeating(
58+
eq(AlarmManager.RTC_WAKEUP),
59+
triggerCaptor.capture(),
60+
eq(TimeUnit.DAYS.toMillis(3)),
61+
eq(pendingIntent)
62+
);
63+
64+
long interval = TimeUnit.DAYS.toMillis(3);
65+
long triggerTime = triggerCaptor.getValue();
66+
long scheduledOrigin = triggerTime - interval;
67+
assertTrue(scheduledOrigin >= beforeCall);
68+
assertTrue(scheduledOrigin <= afterCall);
69+
70+
Intent intent = capturedIntent.get();
71+
assertNotNull("PendingIntent should target the usage receiver", intent);
72+
assertNotNull("PendingIntent should include a component", intent.getComponent());
73+
assertEquals(AppUsageNotificationReceiver.class.getName(), intent.getComponent().getClassName());
74+
}
75+
}
76+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.d4rk.androidtutorials.java.notifications.receivers;
2+
3+
import android.content.Context;
4+
import android.content.Intent;
5+
6+
import androidx.work.OneTimeWorkRequest;
7+
import androidx.work.WorkManager;
8+
9+
import com.d4rk.androidtutorials.java.notifications.workers.AppUsageNotificationWorker;
10+
11+
import org.junit.Test;
12+
import org.mockito.ArgumentCaptor;
13+
import org.mockito.MockedStatic;
14+
15+
import static org.junit.Assert.assertEquals;
16+
import static org.mockito.ArgumentMatchers.any;
17+
import static org.mockito.Mockito.mock;
18+
import static org.mockito.Mockito.mockStatic;
19+
import static org.mockito.Mockito.verify;
20+
21+
public class AppUsageNotificationReceiverTest {
22+
23+
@Test
24+
public void onReceive_enqueuesAppUsageWorker() {
25+
Context context = mock(Context.class);
26+
WorkManager workManager = mock(WorkManager.class);
27+
try (MockedStatic<WorkManager> mockedWorkManager = mockStatic(WorkManager.class)) {
28+
mockedWorkManager.when(() -> WorkManager.getInstance(context)).thenReturn(workManager);
29+
30+
AppUsageNotificationReceiver receiver = new AppUsageNotificationReceiver();
31+
receiver.onReceive(context, new Intent());
32+
33+
ArgumentCaptor<OneTimeWorkRequest> requestCaptor = ArgumentCaptor.forClass(OneTimeWorkRequest.class);
34+
verify(workManager).enqueue(requestCaptor.capture());
35+
36+
OneTimeWorkRequest request = requestCaptor.getValue();
37+
assertEquals(AppUsageNotificationWorker.class.getName(), request.getWorkSpec().workerClassName);
38+
}
39+
}
40+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package com.d4rk.androidtutorials.java.notifications.workers;
2+
3+
import android.app.Notification;
4+
import android.app.NotificationChannel;
5+
import android.app.NotificationManager;
6+
import android.content.Context;
7+
import android.content.SharedPreferences;
8+
9+
import androidx.preference.PreferenceManager;
10+
import androidx.work.ListenableWorker;
11+
import androidx.work.WorkerParameters;
12+
import androidx.core.app.NotificationCompat;
13+
14+
import com.d4rk.androidtutorials.java.R;
15+
16+
import org.junit.Test;
17+
import org.mockito.ArgumentCaptor;
18+
import org.mockito.MockedConstruction;
19+
import org.mockito.MockedStatic;
20+
import org.mockito.Mockito;
21+
22+
import java.util.concurrent.TimeUnit;
23+
24+
import static org.junit.Assert.assertEquals;
25+
import static org.junit.Assert.assertTrue;
26+
import static org.mockito.ArgumentMatchers.any;
27+
import static org.mockito.ArgumentMatchers.anyBoolean;
28+
import static org.mockito.ArgumentMatchers.anyInt;
29+
import static org.mockito.ArgumentMatchers.anyLong;
30+
import static org.mockito.ArgumentMatchers.anyString;
31+
import static org.mockito.ArgumentMatchers.eq;
32+
import static org.mockito.Mockito.mock;
33+
import static org.mockito.Mockito.mockStatic;
34+
import static org.mockito.Mockito.never;
35+
import static org.mockito.Mockito.verify;
36+
import static org.mockito.Mockito.when;
37+
38+
public class AppUsageNotificationWorkerTest {
39+
40+
@Test
41+
public void doWork_whenLastUsedExceedsThreshold_createsChannelAndNotifies() {
42+
Context context = mock(Context.class);
43+
when(context.getApplicationContext()).thenReturn(context);
44+
NotificationManager notificationManager = mock(NotificationManager.class);
45+
when(context.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(notificationManager);
46+
47+
SharedPreferences sharedPreferences = mock(SharedPreferences.class);
48+
SharedPreferences.Editor editor = mock(SharedPreferences.Editor.class);
49+
when(sharedPreferences.edit()).thenReturn(editor);
50+
when(editor.putLong(anyString(), anyLong())).thenReturn(editor);
51+
52+
long threshold = TimeUnit.DAYS.toMillis(3);
53+
long currentTime = System.currentTimeMillis();
54+
when(sharedPreferences.getLong(eq("lastUsed"), anyLong())).thenReturn(currentTime - threshold - 1L);
55+
56+
WorkerParameters parameters = mock(WorkerParameters.class);
57+
58+
try (MockedStatic<PreferenceManager> mockedPreferences = mockStatic(PreferenceManager.class)) {
59+
mockedPreferences.when(() -> PreferenceManager.getDefaultSharedPreferences(context))
60+
.thenReturn(sharedPreferences);
61+
62+
when(context.getString(R.string.app_usage_notifications)).thenReturn("App usage");
63+
when(context.getString(R.string.notification_last_time_used_title)).thenReturn("We miss you");
64+
when(context.getString(R.string.summary_notification_last_time_used)).thenReturn("Come back soon");
65+
66+
Notification notification = mock(Notification.class);
67+
68+
try (MockedConstruction<NotificationCompat.Builder> mockedBuilder =
69+
Mockito.mockConstruction(NotificationCompat.Builder.class, (builderMock, contextData) -> {
70+
when(builderMock.setSmallIcon(anyInt())).thenReturn(builderMock);
71+
when(builderMock.setContentTitle(any(CharSequence.class))).thenReturn(builderMock);
72+
when(builderMock.setContentText(any(CharSequence.class))).thenReturn(builderMock);
73+
when(builderMock.setAutoCancel(anyBoolean())).thenReturn(builderMock);
74+
when(builderMock.build()).thenReturn(notification);
75+
})) {
76+
77+
AppUsageNotificationWorker worker = new AppUsageNotificationWorker(context, parameters);
78+
ListenableWorker.Result result = worker.doWork();
79+
80+
assertEquals(ListenableWorker.Result.success(), result);
81+
82+
ArgumentCaptor<NotificationChannel> channelCaptor = ArgumentCaptor.forClass(NotificationChannel.class);
83+
verify(notificationManager).createNotificationChannel(channelCaptor.capture());
84+
NotificationChannel channel = channelCaptor.getValue();
85+
assertEquals("app_usage_channel", channel.getId());
86+
assertEquals("App usage", channel.getName().toString());
87+
88+
verify(notificationManager).notify(eq(0), eq(notification));
89+
90+
ArgumentCaptor<Long> timestampCaptor = ArgumentCaptor.forClass(Long.class);
91+
verify(editor).putLong(eq("lastUsed"), timestampCaptor.capture());
92+
assertTrue(timestampCaptor.getValue() >= currentTime);
93+
verify(editor).apply();
94+
95+
assertEquals(1, mockedBuilder.constructed().size());
96+
}
97+
}
98+
}
99+
100+
@Test
101+
public void doWork_whenLastUsedWithinThreshold_skipsNotification() {
102+
Context context = mock(Context.class);
103+
when(context.getApplicationContext()).thenReturn(context);
104+
NotificationManager notificationManager = mock(NotificationManager.class);
105+
when(context.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(notificationManager);
106+
107+
SharedPreferences sharedPreferences = mock(SharedPreferences.class);
108+
SharedPreferences.Editor editor = mock(SharedPreferences.Editor.class);
109+
when(sharedPreferences.edit()).thenReturn(editor);
110+
when(editor.putLong(anyString(), anyLong())).thenReturn(editor);
111+
112+
long threshold = TimeUnit.DAYS.toMillis(3);
113+
long currentTime = System.currentTimeMillis();
114+
when(sharedPreferences.getLong(eq("lastUsed"), anyLong())).thenReturn(currentTime - threshold + 1L);
115+
116+
WorkerParameters parameters = mock(WorkerParameters.class);
117+
118+
try (MockedStatic<PreferenceManager> mockedPreferences = mockStatic(PreferenceManager.class)) {
119+
mockedPreferences.when(() -> PreferenceManager.getDefaultSharedPreferences(context))
120+
.thenReturn(sharedPreferences);
121+
122+
when(context.getString(R.string.app_usage_notifications)).thenReturn("App usage");
123+
when(context.getString(R.string.notification_last_time_used_title)).thenReturn("We miss you");
124+
when(context.getString(R.string.summary_notification_last_time_used)).thenReturn("Come back soon");
125+
126+
try (MockedConstruction<NotificationCompat.Builder> mockedBuilder =
127+
Mockito.mockConstruction(NotificationCompat.Builder.class)) {
128+
AppUsageNotificationWorker worker = new AppUsageNotificationWorker(context, parameters);
129+
ListenableWorker.Result result = worker.doWork();
130+
131+
assertEquals(ListenableWorker.Result.success(), result);
132+
133+
verify(notificationManager, never()).createNotificationChannel(any(NotificationChannel.class));
134+
verify(notificationManager, never()).notify(anyInt(), any(Notification.class));
135+
136+
verify(editor).putLong(eq("lastUsed"), anyLong());
137+
verify(editor).apply();
138+
139+
assertTrue(mockedBuilder.constructed().isEmpty());
140+
}
141+
}
142+
}
143+
}

0 commit comments

Comments
 (0)