Skip to content

Commit 4e13530

Browse files
Add tests for app usage notification worker
1 parent 31e7acb commit 4e13530

File tree

2 files changed

+232
-5
lines changed

2 files changed

+232
-5
lines changed

app/src/main/java/com/d4rk/androidtutorials/java/notifications/workers/AppUsageNotificationWorker.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
import android.os.Build;
88

99
import androidx.annotation.NonNull;
10+
import androidx.annotation.Nullable;
1011
import androidx.annotation.RequiresApi;
12+
import androidx.annotation.VisibleForTesting;
1113
import androidx.core.app.NotificationCompat;
1214
import androidx.preference.PreferenceManager;
1315
import androidx.work.Worker;
@@ -22,6 +24,8 @@
2224
public class AppUsageNotificationWorker extends Worker {
2325

2426
private final SharedPreferences sharedPreferences;
27+
@Nullable
28+
private final NotificationManager notificationManager;
2529

2630
/**
2731
* Constructor for {@link AppUsageNotificationWorker}.
@@ -31,8 +35,20 @@ public class AppUsageNotificationWorker extends Worker {
3135
*/
3236
public AppUsageNotificationWorker(@NonNull Context context,
3337
@NonNull WorkerParameters workerParams) {
38+
this(context,
39+
workerParams,
40+
PreferenceManager.getDefaultSharedPreferences(context),
41+
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE));
42+
}
43+
44+
@VisibleForTesting
45+
AppUsageNotificationWorker(@NonNull Context context,
46+
@NonNull WorkerParameters workerParams,
47+
@NonNull SharedPreferences sharedPreferences,
48+
@Nullable NotificationManager notificationManager) {
3449
super(context, workerParams);
35-
this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
50+
this.sharedPreferences = sharedPreferences;
51+
this.notificationManager = notificationManager;
3652
}
3753

3854
/**
@@ -49,10 +65,8 @@ public Result doWork() {
4965
long notificationThreshold = 3 * 24 * 60 * 60 * 1000;
5066
long lastUsedTimestamp = sharedPreferences.getLong("lastUsed", 0);
5167

52-
if (currentTimestamp - lastUsedTimestamp > notificationThreshold) {
53-
NotificationManager notificationManager =
54-
(NotificationManager) getApplicationContext().getSystemService(
55-
Context.NOTIFICATION_SERVICE);
68+
if (lastUsedTimestamp != 0 && notificationManager != null
69+
&& currentTimestamp - lastUsedTimestamp > notificationThreshold) {
5670
String appUsageChannelId = "app_usage_channel";
5771
NotificationChannel appUsageChannel = new NotificationChannel(
5872
appUsageChannelId,
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
package com.d4rk.androidtutorials.java.notifications.workers;
2+
3+
import static org.junit.Assert.assertTrue;
4+
import static org.mockito.ArgumentMatchers.any;
5+
import static org.mockito.ArgumentMatchers.anyInt;
6+
import static org.mockito.ArgumentMatchers.anyLong;
7+
import static org.mockito.ArgumentMatchers.anyString;
8+
import static org.mockito.ArgumentMatchers.eq;
9+
import static org.mockito.Mockito.never;
10+
import static org.mockito.Mockito.verify;
11+
import static org.mockito.Mockito.when;
12+
13+
import android.app.Notification;
14+
import android.app.NotificationChannel;
15+
import android.app.NotificationManager;
16+
import android.content.Context;
17+
import android.content.SharedPreferences;
18+
import android.content.pm.ApplicationInfo;
19+
import android.content.res.Resources;
20+
import android.os.Build;
21+
import android.test.mock.MockContext;
22+
23+
import androidx.work.WorkerParameters;
24+
25+
import com.d4rk.androidtutorials.java.R;
26+
27+
import org.junit.After;
28+
import org.junit.Before;
29+
import org.junit.Test;
30+
import org.mockito.ArgumentCaptor;
31+
import org.mockito.Mock;
32+
import org.mockito.MockitoAnnotations;
33+
34+
import java.util.concurrent.TimeUnit;
35+
36+
/**
37+
* Tests for {@link AppUsageNotificationWorker}.
38+
*/
39+
public class AppUsageNotificationWorkerTest {
40+
41+
private static final String PACKAGE_NAME = "com.d4rk.androidtutorials.java";
42+
43+
private AutoCloseable closeable;
44+
45+
@Mock
46+
private SharedPreferences sharedPreferences;
47+
48+
@Mock
49+
private SharedPreferences.Editor editor;
50+
51+
@Mock
52+
private NotificationManager notificationManager;
53+
54+
@Mock
55+
private WorkerParameters workerParameters;
56+
57+
@Mock
58+
private Resources resources;
59+
60+
@Before
61+
public void setUp() {
62+
closeable = MockitoAnnotations.openMocks(this);
63+
64+
when(sharedPreferences.edit()).thenReturn(editor);
65+
when(editor.putLong(anyString(), anyLong())).thenReturn(editor);
66+
67+
when(resources.getString(R.string.app_usage_notifications))
68+
.thenReturn("App usage notifications");
69+
when(resources.getString(R.string.notification_last_time_used_title))
70+
.thenReturn("We miss you");
71+
when(resources.getString(R.string.summary_notification_last_time_used))
72+
.thenReturn("Come back and code with us");
73+
when(resources.getResourcePackageName(R.drawable.ic_notification_important))
74+
.thenReturn(PACKAGE_NAME);
75+
when(resources.getResourceTypeName(R.drawable.ic_notification_important))
76+
.thenReturn("drawable");
77+
when(resources.getResourceEntryName(R.drawable.ic_notification_important))
78+
.thenReturn("ic_notification_important");
79+
when(resources.getResourceName(R.drawable.ic_notification_important))
80+
.thenReturn(PACKAGE_NAME + ":drawable/ic_notification_important");
81+
}
82+
83+
@After
84+
public void tearDown() throws Exception {
85+
closeable.close();
86+
}
87+
88+
@Test
89+
public void doWork_postsNotificationWhenBeyondThreshold() {
90+
long threshold = TimeUnit.DAYS.toMillis(3);
91+
long lastUsed = System.currentTimeMillis() - threshold - TimeUnit.HOURS.toMillis(1);
92+
when(sharedPreferences.getLong(eq("lastUsed"), anyLong())).thenReturn(lastUsed);
93+
94+
AppUsageNotificationWorker worker = createWorker();
95+
96+
worker.doWork();
97+
98+
verify(sharedPreferences).getLong("lastUsed", 0);
99+
verify(notificationManager).createNotificationChannel(any(NotificationChannel.class));
100+
verify(notificationManager).notify(eq(0), any(Notification.class));
101+
102+
ArgumentCaptor<Long> timestampCaptor = ArgumentCaptor.forClass(Long.class);
103+
verify(editor).putLong(eq("lastUsed"), timestampCaptor.capture());
104+
verify(editor).apply();
105+
106+
long updatedTimestamp = timestampCaptor.getValue();
107+
assertTrue(updatedTimestamp > lastUsed);
108+
}
109+
110+
@Test
111+
public void doWork_skipsNotificationWhenWithinThreshold() {
112+
long threshold = TimeUnit.DAYS.toMillis(3);
113+
long lastUsed = System.currentTimeMillis() - threshold + TimeUnit.HOURS.toMillis(2);
114+
when(sharedPreferences.getLong(eq("lastUsed"), anyLong())).thenReturn(lastUsed);
115+
116+
AppUsageNotificationWorker worker = createWorker();
117+
118+
worker.doWork();
119+
120+
verify(sharedPreferences).getLong("lastUsed", 0);
121+
verify(notificationManager, never()).createNotificationChannel(any(NotificationChannel.class));
122+
verify(notificationManager, never()).notify(anyInt(), any(Notification.class));
123+
124+
ArgumentCaptor<Long> timestampCaptor = ArgumentCaptor.forClass(Long.class);
125+
verify(editor).putLong(eq("lastUsed"), timestampCaptor.capture());
126+
verify(editor).apply();
127+
128+
long updatedTimestamp = timestampCaptor.getValue();
129+
assertTrue(updatedTimestamp > lastUsed);
130+
}
131+
132+
@Test
133+
public void doWork_skipsNotificationWhenTimestampMissing() {
134+
when(sharedPreferences.getLong(eq("lastUsed"), anyLong())).thenReturn(0L);
135+
136+
AppUsageNotificationWorker worker = createWorker();
137+
138+
worker.doWork();
139+
140+
verify(sharedPreferences).getLong("lastUsed", 0);
141+
verify(notificationManager, never()).createNotificationChannel(any(NotificationChannel.class));
142+
verify(notificationManager, never()).notify(anyInt(), any(Notification.class));
143+
144+
ArgumentCaptor<Long> timestampCaptor = ArgumentCaptor.forClass(Long.class);
145+
verify(editor).putLong(eq("lastUsed"), timestampCaptor.capture());
146+
verify(editor).apply();
147+
148+
long updatedTimestamp = timestampCaptor.getValue();
149+
assertTrue(updatedTimestamp > 0L);
150+
}
151+
152+
private AppUsageNotificationWorker createWorker() {
153+
TestContext context = new TestContext(sharedPreferences, notificationManager, resources);
154+
return new AppUsageNotificationWorker(context, workerParameters, sharedPreferences, notificationManager);
155+
}
156+
157+
private static class TestContext extends MockContext {
158+
159+
private final SharedPreferences sharedPreferences;
160+
private final NotificationManager notificationManager;
161+
private final Resources resources;
162+
private final ApplicationInfo applicationInfo;
163+
164+
TestContext(SharedPreferences sharedPreferences,
165+
NotificationManager notificationManager,
166+
Resources resources) {
167+
this.sharedPreferences = sharedPreferences;
168+
this.notificationManager = notificationManager;
169+
this.resources = resources;
170+
this.applicationInfo = new ApplicationInfo();
171+
this.applicationInfo.packageName = PACKAGE_NAME;
172+
this.applicationInfo.targetSdkVersion = Build.VERSION_CODES.O;
173+
}
174+
175+
@Override
176+
public Context getApplicationContext() {
177+
return this;
178+
}
179+
180+
@Override
181+
public SharedPreferences getSharedPreferences(String name, int mode) {
182+
return sharedPreferences;
183+
}
184+
185+
@Override
186+
public Object getSystemService(String name) {
187+
if (Context.NOTIFICATION_SERVICE.equals(name)) {
188+
return notificationManager;
189+
}
190+
return super.getSystemService(name);
191+
}
192+
193+
@Override
194+
public Resources getResources() {
195+
return resources;
196+
}
197+
198+
@Override
199+
public String getPackageName() {
200+
return PACKAGE_NAME;
201+
}
202+
203+
@Override
204+
public ApplicationInfo getApplicationInfo() {
205+
return applicationInfo;
206+
}
207+
208+
@Override
209+
public String getOpPackageName() {
210+
return PACKAGE_NAME;
211+
}
212+
}
213+
}

0 commit comments

Comments
 (0)