package com.mopub.nativeads; import android.os.Handler; import android.os.SystemClock; import com.mopub.common.test.support.SdkTestRunner; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.robolectric.shadows.ShadowSystemClock; import java.util.ArrayList; import static com.mopub.nativeads.NativeAdSource.AdSourceListener; import static org.fest.assertions.api.Assertions.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @RunWith(SdkTestRunner.class) public class NativeAdSourceTest { private NativeAdSource subject; private ArrayList<TimestampWrapper<NativeResponse>> nativeAdCache; private RequestParameters requestParameters; private int defaultRetryTime; private int maxRetryTime; @Mock private AdSourceListener mockAdSourceListener; @Mock private MoPubNative mockMoPubNative; @Mock private NativeResponse mockNativeResponse; @Mock private Handler mockReplenishCacheHandler; @Before public void setUp() { nativeAdCache = new ArrayList<TimestampWrapper<NativeResponse>>(2); subject = new NativeAdSource(nativeAdCache, mockReplenishCacheHandler); subject.setAdSourceListener(mockAdSourceListener); requestParameters = new RequestParameters.Builder().build(); defaultRetryTime = 1000; maxRetryTime = 5*60*1000; // XXX We need this to ensure that our SystemClock starts ShadowSystemClock.uptimeMillis(); } @Test public void constructor_shouldInitializeCorrectly() { assertThat(subject.mRequestInFlight).isFalse(); assertThat(subject.mSequenceNumber).isEqualTo(0); assertThat(subject.mRetryTimeMilliseconds).isEqualTo(defaultRetryTime); } @Test public void loadAds_shouldReplenishCache() { subject.loadAds(requestParameters, mockMoPubNative); assertThat(subject.mRequestInFlight).isTrue(); verify(mockMoPubNative).makeRequest(requestParameters, 0); } @Test public void loadAds_shouldClearNativeAdSource() { subject.setMoPubNative(mockMoPubNative); TimestampWrapper<NativeResponse> timestampWrapper = new TimestampWrapper<NativeResponse>(mock(NativeResponse.class)); nativeAdCache.add(timestampWrapper); subject.mRequestInFlight = true; subject.mSequenceNumber = 5; subject.mRetryTimeMilliseconds = maxRetryTime; subject.loadAds(requestParameters, mockMoPubNative); verify(timestampWrapper.mInstance).destroy(); assertThat(nativeAdCache).isEmpty(); verify(mockMoPubNative).destroy(); verify(mockReplenishCacheHandler).removeMessages(0); assertThat(subject.mSequenceNumber).isEqualTo(0); assertThat(subject.mRetryTimeMilliseconds).isEqualTo(defaultRetryTime); // new request has been kicked off assertThat(subject.mRequestInFlight).isTrue(); } @Test public void loadAds_shouldDestroyPreviousMoPubNativeInstance() { subject.loadAds(requestParameters, mockMoPubNative); verify(mockMoPubNative, never()).destroy(); subject.loadAds(requestParameters, mockMoPubNative); verify(mockMoPubNative).destroy(); } @Test public void clear_shouldDestroyMoPubNative_shouldClearNativeAdCache_shouldRemovePollHandlerMessages_shouldResetSequenceNumber_shouldResetRequestInFlight_shouldResetRetryTime() { subject.setMoPubNative(mockMoPubNative); TimestampWrapper<NativeResponse> timestampWrapper = new TimestampWrapper<NativeResponse>(mock(NativeResponse.class)); nativeAdCache.add(timestampWrapper); subject.mRequestInFlight = true; subject.mSequenceNumber = 5; subject.mRetryTimeMilliseconds = maxRetryTime; subject.clear(); verify(timestampWrapper.mInstance).destroy(); assertThat(nativeAdCache).isEmpty(); verify(mockMoPubNative).destroy(); verify(mockReplenishCacheHandler).removeMessages(0); assertThat(subject.mRequestInFlight).isFalse(); assertThat(subject.mSequenceNumber).isEqualTo(0); assertThat(subject.mRetryTimeMilliseconds).isEqualTo(defaultRetryTime); } @Test public void dequeueAd_withNonStaleResponse_shouldReturnNativeResponse() { subject.setMoPubNative(mockMoPubNative); nativeAdCache.add(new TimestampWrapper<NativeResponse>(mockNativeResponse)); assertThat(subject.dequeueAd()).isEqualTo(mockNativeResponse); assertThat(nativeAdCache).isEmpty(); } @Test public void dequeueAd_withStaleResponse_shouldReturnNativeResponse() { subject.setMoPubNative(mockMoPubNative); TimestampWrapper<NativeResponse> timestampWrapper = new TimestampWrapper<NativeResponse>( mockNativeResponse); timestampWrapper.mCreatedTimestamp = SystemClock.uptimeMillis() - (15*60*1000+1); nativeAdCache.add(timestampWrapper); assertThat(subject.dequeueAd()).isNull(); assertThat(nativeAdCache).isEmpty(); } @Test public void dequeueAd_noRequestInFlight_shouldReplenishCache() { subject.setMoPubNative(mockMoPubNative); nativeAdCache.add(new TimestampWrapper<NativeResponse>(mockNativeResponse)); assertThat(subject.dequeueAd()).isEqualTo(mockNativeResponse); assertThat(nativeAdCache).isEmpty(); verify(mockReplenishCacheHandler).post(any(Runnable.class)); } @Test public void dequeueAd_requestInFlight_shouldNotReplenishCache() { subject.setMoPubNative(mockMoPubNative); nativeAdCache.add(new TimestampWrapper<NativeResponse>(mockNativeResponse)); subject.mRequestInFlight = true; assertThat(subject.dequeueAd()).isEqualTo(mockNativeResponse); assertThat(nativeAdCache).isEmpty(); verify(mockReplenishCacheHandler, never()).post(any(Runnable.class)); } @Test public void updateRetryTime_shouldUpdateRetryTimeUntilAt10Minutes() { int retryTime = 0; while (subject.mRetryTimeMilliseconds < maxRetryTime) { subject.updateRetryTime(); retryTime = subject.mRetryTimeMilliseconds; } assertThat(retryTime).isEqualTo(maxRetryTime); // assert it won't change anymore subject.updateRetryTime(); assertThat(retryTime).isEqualTo(subject.mRetryTimeMilliseconds); } @Test public void resetRetryTime_shouldSetRetryTimeTo1Second() { assertThat(subject.mRetryTimeMilliseconds).isEqualTo(defaultRetryTime); subject.updateRetryTime(); assertThat(subject.mRetryTimeMilliseconds).isGreaterThan(defaultRetryTime); subject.resetRetryTime(); assertThat(subject.mRetryTimeMilliseconds).isEqualTo(defaultRetryTime); } @Test public void replenishCache_shouldLoadNativeAd_shouldMarkRequestInFlight() { subject.setMoPubNative(mockMoPubNative); subject.replenishCache(); verify(mockMoPubNative).makeRequest(any(RequestParameters.class), eq(0)); assertThat(subject.mRequestInFlight).isTrue(); } @Test public void replenishCache_withRequestInFlight_shouldNotLoadNativeAd() { subject.mRequestInFlight = true; subject.setMoPubNative(mockMoPubNative); subject.replenishCache(); verify(mockMoPubNative, never()).makeRequest(requestParameters, 0); assertThat(subject.mRequestInFlight).isTrue(); } @Test public void replenishCache_withCacheSizeAtLimit_shouldNotLoadNativeAd() { // Default cache size may change in the future and this test will have to be updated nativeAdCache.add(mock(TimestampWrapper.class)); nativeAdCache.add(mock(TimestampWrapper.class)); nativeAdCache.add(mock(TimestampWrapper.class)); subject.setMoPubNative(mockMoPubNative); subject.replenishCache(); verify(mockMoPubNative, never()).makeRequest(any(RequestParameters.class), any(Integer.class)); assertThat(subject.mRequestInFlight).isFalse(); } @Test public void moPubNativeNetworkListener_onNativeLoad_shouldAddToCache() { subject.setMoPubNative(mockMoPubNative); subject.getMoPubNativeNetworkListener().onNativeLoad(mockNativeResponse); assertThat(nativeAdCache).hasSize(1); assertThat(nativeAdCache.get(0).mInstance).isEqualTo(mockNativeResponse); } @Test public void moPubNativeNetworkListener_onNativeLoad_withEmptyCache_shouldCallOnAdsAvailable() { subject.setMoPubNative(mockMoPubNative); assertThat(nativeAdCache).isEmpty(); subject.getMoPubNativeNetworkListener().onNativeLoad(mockNativeResponse); assertThat(nativeAdCache).hasSize(1); verify(mockAdSourceListener).onAdsAvailable(); } @Test public void moPubNativeNetworkListener_onNativeLoad_withNonEmptyCache_shouldNotCallOnAdsAvailable() { subject.setMoPubNative(mockMoPubNative); nativeAdCache.add(mock(TimestampWrapper.class)); subject.getMoPubNativeNetworkListener().onNativeLoad(mockNativeResponse); assertThat(nativeAdCache).hasSize(2); verify(mockAdSourceListener, never()).onAdsAvailable(); } @Test public void moPubNativeNetworkListener_onNativeLoad_shouldIncrementSequenceNumber_shouldResetRetryTime() { subject.setMoPubNative(mockMoPubNative); subject.mRetryTimeMilliseconds = maxRetryTime; subject.mSequenceNumber = 5; subject.getMoPubNativeNetworkListener().onNativeLoad(mockNativeResponse); assertThat(subject.mRetryTimeMilliseconds).isEqualTo(defaultRetryTime); assertThat(subject.mSequenceNumber).isEqualTo(6); } @Test public void moPubNativeNetworkListener_onNativeLoad_withFullCache_shouldResetRequestInFlight() { subject.setMoPubNative(mockMoPubNative); subject.mRequestInFlight = true; // fill cache nativeAdCache.add(mock(TimestampWrapper.class)); nativeAdCache.add(mock(TimestampWrapper.class)); nativeAdCache.add(mock(TimestampWrapper.class)); subject.getMoPubNativeNetworkListener().onNativeLoad(mockNativeResponse); assertThat(subject.mRequestInFlight).isEqualTo(false); } @Test public void moPubNativeNetworkListener_onNativeLoad_withNonFullCache_shouldReplenishCache() { subject.setMoPubNative(mockMoPubNative); subject.mRequestInFlight = true; subject.getMoPubNativeNetworkListener().onNativeLoad(mockNativeResponse); assertThat(subject.mRequestInFlight).isEqualTo(true); verify(mockMoPubNative).makeRequest(any(RequestParameters.class), eq(1)); } @Test public void moPubNativeNetworkListener_onNativeFail_shouldResetInFlight_shouldUpdateRetryTime_shouldPostDelayedRunnable() { subject.mRequestInFlight = true; subject.mRetryTimeMilliseconds = defaultRetryTime; subject.getMoPubNativeNetworkListener().onNativeFail(NativeErrorCode.UNSPECIFIED); assertThat(subject.mRequestInFlight).isEqualTo(false); assertThat(subject.mRetryInFlight).isEqualTo(true); assertThat(subject.mRetryTimeMilliseconds).isGreaterThan(defaultRetryTime); verify(mockReplenishCacheHandler).postDelayed(any(Runnable.class), eq((long)subject.mRetryTimeMilliseconds)); } @Test public void moPubNativeNetworkListener_onNativeFail_maxRetryTime_shouldResetInflight_shouldResetRetryTime_shouldNotPostDelayedRunnable() { subject.mRequestInFlight = true; subject.mRetryTimeMilliseconds = maxRetryTime; subject.getMoPubNativeNetworkListener().onNativeFail(NativeErrorCode.UNSPECIFIED); assertThat(subject.mRequestInFlight).isEqualTo(false); assertThat(subject.mRetryInFlight).isEqualTo(false); assertThat(subject.mRetryTimeMilliseconds).isEqualTo(defaultRetryTime); verify(mockReplenishCacheHandler, never()).postDelayed(any(Runnable.class), anyLong()); } }