Skip to content

Commit 445bdf8

Browse files
Add tests for ad loaders and improve main thread handling
1 parent 31e7acb commit 445bdf8

File tree

3 files changed

+427
-8
lines changed

3 files changed

+427
-8
lines changed

app/src/main/java/com/d4rk/androidtutorials/java/ads/managers/NativeAdLoader.java

Lines changed: 105 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.d4rk.androidtutorials.java.ads.managers;
22

33
import android.content.Context;
4+
import android.os.Handler;
5+
import android.os.Looper;
46
import android.util.Log;
57
import android.view.LayoutInflater;
68
import android.view.View;
@@ -11,6 +13,7 @@
1113

1214
import androidx.annotation.LayoutRes;
1315
import androidx.annotation.NonNull;
16+
import androidx.annotation.VisibleForTesting;
1417

1518
import com.d4rk.androidtutorials.java.R;
1619
import com.google.android.gms.ads.AdListener;
@@ -49,7 +52,7 @@ public static void load(@NonNull Context context,
4952
@NonNull AdRequest adRequest,
5053
@androidx.annotation.Nullable AdListener listener) {
5154
AdLoader.Builder builder = new AdLoader.Builder(context, context.getString(R.string.native_ad_banner_unit_id))
52-
.forNativeAd(nativeAd -> {
55+
.forNativeAd(nativeAd -> postToMainThread(() -> {
5356
LayoutInflater inflater = LayoutInflater.from(context);
5457
NativeAdView adView = (NativeAdView) inflater.inflate(layoutRes, container, false);
5558
adView.setLayoutParams(new ViewGroup.LayoutParams(
@@ -59,22 +62,116 @@ public static void load(@NonNull Context context,
5962
container.getPaddingRight(), container.getPaddingBottom());
6063
container.setPadding(0, 0, 0, 0);
6164
populateNativeAdView(nativeAd, adView);
65+
container.setVisibility(View.VISIBLE);
6266
container.removeAllViews();
6367
container.addView(adView);
6468
container.requestLayout();
69+
}));
70+
71+
builder.withAdListener(createAdListener(container, listener));
72+
73+
AdLoader adLoader = builder.build();
74+
adLoader.loadAd(adRequest);
75+
}
76+
77+
private static AdListener createAdListener(@NonNull ViewGroup container,
78+
@androidx.annotation.Nullable AdListener listener) {
79+
return new AdListener() {
80+
@Override
81+
public void onAdLoaded() {
82+
postToMainThread(() -> {
83+
if (listener != null) {
84+
listener.onAdLoaded();
85+
}
6586
});
87+
}
6688

67-
builder.withAdListener(listener != null ? listener : new AdListener() {
6889
@Override
6990
public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {
70-
Log.w(TAG, "Failed to load native ad: " + loadAdError.getMessage());
71-
container.removeAllViews();
72-
container.setVisibility(View.GONE);
91+
postToMainThread(() -> {
92+
Log.w(TAG, "Failed to load native ad: " + loadAdError.getMessage());
93+
container.removeAllViews();
94+
container.setVisibility(View.GONE);
95+
if (listener != null) {
96+
listener.onAdFailedToLoad(loadAdError);
97+
}
98+
});
7399
}
74-
});
75100

76-
AdLoader adLoader = builder.build();
77-
adLoader.loadAd(adRequest);
101+
@Override
102+
public void onAdOpened() {
103+
postToMainThread(() -> {
104+
if (listener != null) {
105+
listener.onAdOpened();
106+
}
107+
});
108+
}
109+
110+
@Override
111+
public void onAdClosed() {
112+
postToMainThread(() -> {
113+
if (listener != null) {
114+
listener.onAdClosed();
115+
}
116+
});
117+
}
118+
119+
@Override
120+
public void onAdClicked() {
121+
postToMainThread(() -> {
122+
if (listener != null) {
123+
listener.onAdClicked();
124+
}
125+
});
126+
}
127+
128+
@Override
129+
public void onAdImpression() {
130+
postToMainThread(() -> {
131+
if (listener != null) {
132+
listener.onAdImpression();
133+
}
134+
});
135+
}
136+
};
137+
}
138+
139+
interface MainThreadExecutor {
140+
void post(@NonNull Runnable runnable);
141+
}
142+
143+
private static final class HandlerMainThreadExecutor implements MainThreadExecutor {
144+
private final Handler handler;
145+
146+
private HandlerMainThreadExecutor() {
147+
Looper looper = Looper.getMainLooper();
148+
handler = looper != null ? new Handler(looper) : null;
149+
}
150+
151+
@Override
152+
public void post(@NonNull Runnable runnable) {
153+
if (handler != null) {
154+
handler.post(runnable);
155+
} else {
156+
runnable.run();
157+
}
158+
}
159+
}
160+
161+
private static MainThreadExecutor mainThreadExecutor = new HandlerMainThreadExecutor();
162+
163+
private static void postToMainThread(@NonNull Runnable runnable) {
164+
mainThreadExecutor.post(runnable);
165+
}
166+
167+
@VisibleForTesting
168+
static void setMainThreadExecutorForTesting(@NonNull MainThreadExecutor executor) {
169+
mainThreadExecutor = executor;
170+
}
171+
172+
@VisibleForTesting
173+
static void resetMainThreadExecutorForTesting() {
174+
mainThreadExecutor = new HandlerMainThreadExecutor();
78175
}
79176

80177
private static void populateNativeAdView(@NonNull NativeAd nativeAd, @NonNull NativeAdView adView) {

app/src/test/java/com/d4rk/androidtutorials/java/ads/managers/AppOpenAdManagerTest.java

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.d4rk.androidtutorials.java.ads.managers;
22

33
import static org.junit.Assert.assertFalse;
4+
import static org.junit.Assert.assertNotNull;
5+
import static org.junit.Assert.assertNull;
46
import static org.junit.Assert.assertTrue;
57
import static org.mockito.ArgumentMatchers.any;
68
import static org.mockito.ArgumentMatchers.anyString;
@@ -16,8 +18,10 @@
1618

1719
import com.d4rk.androidtutorials.java.ads.AdUtils;
1820
import com.d4rk.androidtutorials.java.ads.managers.AppOpenAd.OnShowAdCompleteListener;
21+
import com.google.android.gms.ads.AdError;
1922
import com.google.android.gms.ads.AdRequest;
2023
import com.google.android.gms.ads.FullScreenContentCallback;
24+
import com.google.android.gms.ads.LoadAdError;
2125
import com.google.android.gms.ads.appopen.AppOpenAd.AppOpenAdLoadCallback;
2226

2327
import org.junit.Before;
@@ -131,6 +135,101 @@ public void showAdIfAvailable_withAd_doesNotShowTwiceWhileShowing() throws Excep
131135
verify(ad, times(1)).show(activity);
132136
}
133137

138+
@Test
139+
public void showAdIfAvailable_withAd_handlesDismissAndReloads() throws Exception {
140+
Activity activity = mock(Activity.class);
141+
OnShowAdCompleteListener listener = mock(OnShowAdCompleteListener.class);
142+
com.google.android.gms.ads.appopen.AppOpenAd ad = mock(com.google.android.gms.ads.appopen.AppOpenAd.class);
143+
144+
setField("appOpenAd", ad);
145+
setLongField("loadTime", System.currentTimeMillis());
146+
147+
try (MockedStatic<AdUtils> adUtils = mockStatic(AdUtils.class);
148+
MockedStatic<com.google.android.gms.ads.appopen.AppOpenAd> appOpenAdStatic =
149+
mockStatic(com.google.android.gms.ads.appopen.AppOpenAd.class)) {
150+
appOpenAdStatic
151+
.when(() -> com.google.android.gms.ads.appopen.AppOpenAd.load(
152+
any(Context.class),
153+
anyString(),
154+
any(AdRequest.class),
155+
any(AppOpenAdLoadCallback.class)))
156+
.thenAnswer(invocation -> {
157+
AppOpenAdLoadCallback callback = invocation.getArgument(3);
158+
callback.onAdLoaded(mock(com.google.android.gms.ads.appopen.AppOpenAd.class));
159+
return null;
160+
});
161+
162+
invokeShowAdIfAvailable(activity, listener);
163+
164+
ArgumentCaptor<FullScreenContentCallback> callbackCaptor =
165+
ArgumentCaptor.forClass(FullScreenContentCallback.class);
166+
verify(ad).setFullScreenContentCallback(callbackCaptor.capture());
167+
verify(ad).show(activity);
168+
assertTrue(getBooleanField("isShowingAd"));
169+
170+
callbackCaptor.getValue().onAdDismissedFullScreenContent();
171+
172+
assertFalse(getBooleanField("isShowingAd"));
173+
assertFalse(getBooleanField("isLoadingAd"));
174+
assertNotNull(getFieldValue("appOpenAd"));
175+
verify(listener, times(1)).onShowAdComplete();
176+
adUtils.verify(() -> AdUtils.initialize(any(Context.class)));
177+
appOpenAdStatic.verify(() -> com.google.android.gms.ads.appopen.AppOpenAd.load(
178+
any(Context.class),
179+
anyString(),
180+
any(AdRequest.class),
181+
any(AppOpenAdLoadCallback.class)));
182+
}
183+
}
184+
185+
@Test
186+
public void showAdIfAvailable_withAd_handlesShowFailureAndReloadFailure() throws Exception {
187+
Activity activity = mock(Activity.class);
188+
OnShowAdCompleteListener listener = mock(OnShowAdCompleteListener.class);
189+
com.google.android.gms.ads.appopen.AppOpenAd ad = mock(com.google.android.gms.ads.appopen.AppOpenAd.class);
190+
LoadAdError loadAdError = mock(LoadAdError.class);
191+
AdError adError = mock(AdError.class);
192+
193+
setField("appOpenAd", ad);
194+
setLongField("loadTime", System.currentTimeMillis());
195+
196+
try (MockedStatic<AdUtils> adUtils = mockStatic(AdUtils.class);
197+
MockedStatic<com.google.android.gms.ads.appopen.AppOpenAd> appOpenAdStatic =
198+
mockStatic(com.google.android.gms.ads.appopen.AppOpenAd.class)) {
199+
appOpenAdStatic
200+
.when(() -> com.google.android.gms.ads.appopen.AppOpenAd.load(
201+
any(Context.class),
202+
anyString(),
203+
any(AdRequest.class),
204+
any(AppOpenAdLoadCallback.class)))
205+
.thenAnswer(invocation -> {
206+
AppOpenAdLoadCallback callback = invocation.getArgument(3);
207+
callback.onAdFailedToLoad(loadAdError);
208+
return null;
209+
});
210+
211+
invokeShowAdIfAvailable(activity, listener);
212+
213+
ArgumentCaptor<FullScreenContentCallback> callbackCaptor =
214+
ArgumentCaptor.forClass(FullScreenContentCallback.class);
215+
verify(ad).setFullScreenContentCallback(callbackCaptor.capture());
216+
verify(ad).show(activity);
217+
218+
callbackCaptor.getValue().onAdFailedToShowFullScreenContent(adError);
219+
220+
assertFalse(getBooleanField("isShowingAd"));
221+
assertFalse(getBooleanField("isLoadingAd"));
222+
assertNull(getFieldValue("appOpenAd"));
223+
verify(listener, times(1)).onShowAdComplete();
224+
adUtils.verify(() -> AdUtils.initialize(any(Context.class)));
225+
appOpenAdStatic.verify(() -> com.google.android.gms.ads.appopen.AppOpenAd.load(
226+
any(Context.class),
227+
anyString(),
228+
any(AdRequest.class),
229+
any(AppOpenAdLoadCallback.class)));
230+
}
231+
}
232+
134233
private Class<?> findManagerClass() {
135234
for (Class<?> clazz : AppOpenAd.class.getDeclaredClasses()) {
136235
if ("AppOpenAdManager".equals(clazz.getSimpleName())) {
@@ -156,6 +255,18 @@ private void invokeShowAdIfAvailable(Activity activity, OnShowAdCompleteListener
156255
method.invoke(manager, activity, listener);
157256
}
158257

258+
private boolean getBooleanField(String fieldName) throws Exception {
259+
Field field = managerClass.getDeclaredField(fieldName);
260+
field.setAccessible(true);
261+
return field.getBoolean(manager);
262+
}
263+
264+
private Object getFieldValue(String fieldName) throws Exception {
265+
Field field = managerClass.getDeclaredField(fieldName);
266+
field.setAccessible(true);
267+
return field.get(manager);
268+
}
269+
159270
private void setField(String fieldName, Object value) throws Exception {
160271
Field field = managerClass.getDeclaredField(fieldName);
161272
field.setAccessible(true);

0 commit comments

Comments
 (0)