Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import sg.bigo.ads.api.AdLoadListener
import sg.bigo.ads.api.InterstitialAd
import sg.bigo.ads.api.InterstitialAdLoader
import sg.bigo.ads.api.InterstitialAdRequest
import sg.bigo.ads.api.RewardVideoAd
import sg.bigo.ads.api.RewardVideoAdLoader
import sg.bigo.ads.api.RewardVideoAdRequest

/**
* Wrapper singleton to enable mocking of Bigo different ad formats for unit testing.
Expand All @@ -44,14 +47,34 @@ object BigoFactory {
instance?.loadAd(adRequest)
}
}

override fun createRewardVideoAdRequest(bidResponse: String, slotId: String) =
RewardVideoAdRequest.Builder().withBid(bidResponse).withSlotId(slotId).build()

override fun createRewardVideoAdLoader() =
object : BigoRewardVideoAdLoaderWrapper {
private var instance: RewardVideoAdLoader? = null

override fun initializeAdLoader(loadListener: AdLoadListener<RewardVideoAd>) {
instance = RewardVideoAdLoader.Builder().withAdLoadListener(loadListener).build()
}

override fun loadAd(adRequest: RewardVideoAdRequest) {
instance?.loadAd(adRequest)
}
}
}
}

/** Declares the methods that will invoke the Bigo SDK */
interface SdkFactory {
fun createInterstitialAdRequest(bidResponse: String, slotId: String): InterstitialAdRequest

fun createRewardVideoAdRequest(bidResponse: String, slotId: String): RewardVideoAdRequest

fun createInterstitialAdLoader(): BigoInterstitialAdLoaderWrapper

fun createRewardVideoAdLoader(): BigoRewardVideoAdLoaderWrapper
}

/**
Expand All @@ -64,3 +87,14 @@ interface BigoInterstitialAdLoaderWrapper {

fun loadAd(adRequest: InterstitialAdRequest)
}

/**
* Declares the methods that will invoke the [RewardVideoAdLoader] methods
*
* This wrapper is needed to enable mocking of AdLoader operations and use it for unit testing.
*/
interface BigoRewardVideoAdLoaderWrapper {
fun initializeAdLoader(loadListener: AdLoadListener<RewardVideoAd>)

fun loadAd(adRequest: RewardVideoAdRequest)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,79 @@
package com.google.ads.mediation.bigo

import android.content.Context
import com.google.ads.mediation.bigo.BigoMediationAdapter.Companion.ADAPTER_ERROR_DOMAIN
import com.google.ads.mediation.bigo.BigoMediationAdapter.Companion.ERROR_CODE_MISSING_SLOT_ID
import com.google.ads.mediation.bigo.BigoMediationAdapter.Companion.ERROR_MSG_MISSING_SLOT_ID
import com.google.ads.mediation.bigo.BigoMediationAdapter.Companion.SDK_ERROR_DOMAIN
import com.google.ads.mediation.bigo.BigoMediationAdapter.Companion.SLOT_ID_KEY
import com.google.android.gms.ads.mediation.MediationAdLoadCallback
import com.google.android.gms.ads.mediation.MediationRewardedAd
import com.google.android.gms.ads.mediation.MediationRewardedAdCallback
import com.google.android.gms.ads.mediation.MediationRewardedAdConfiguration
import java.lang.IllegalArgumentException
import sg.bigo.ads.api.AdError
import sg.bigo.ads.api.AdLoadListener
import sg.bigo.ads.api.RewardAdInteractionListener
import sg.bigo.ads.api.RewardVideoAd

/**
* Used to load Bigo rewarded ads and mediate callbacks between Google Mobile Ads SDK and Bigo SDK.
*/
class BigoRewardedAd
private constructor(
private val context: Context,
private val mediationAdLoadCallback:
MediationAdLoadCallback<MediationRewardedAd, MediationRewardedAdCallback>,
// TODO: Add other parameters or remove unnecessary ones.
) : MediationRewardedAd {
private val bidResponse: String,
private val slotId: String,
) : MediationRewardedAd, AdLoadListener<RewardVideoAd>, RewardAdInteractionListener {
private var rewardedAdCallback: MediationRewardedAdCallback? = null
private var rewardVideoAd: RewardVideoAd? = null

fun loadAd() {
// TODO: Implement this method.
val adRequest = BigoFactory.delegate.createRewardVideoAdRequest(bidResponse, slotId)
val rewardVideoAdLoader = BigoFactory.delegate.createRewardVideoAdLoader()
rewardVideoAdLoader.initializeAdLoader(loadListener = this)
rewardVideoAdLoader.loadAd(adRequest)
}

override fun showAd(context: Context) {
// TODO: Implement this method.
rewardVideoAd?.show()
}

override fun onError(adError: AdError) {
val gmaAdError = BigoUtils.getGmaAdError(adError.code, adError.message, SDK_ERROR_DOMAIN)
mediationAdLoadCallback.onFailure(gmaAdError)
}

override fun onAdLoaded(rewardVideoAd: RewardVideoAd) {
rewardVideoAd.setAdInteractionListener(this)
this.rewardVideoAd = rewardVideoAd
rewardedAdCallback = mediationAdLoadCallback.onSuccess(this)
}

override fun onAdRewarded() {
rewardedAdCallback?.onUserEarnedReward()
}

override fun onAdError(adError: AdError) {
val gmaAdError = BigoUtils.getGmaAdError(adError.code, adError.message, SDK_ERROR_DOMAIN)
rewardedAdCallback?.onAdFailedToShow(gmaAdError)
}

override fun onAdImpression() {
rewardedAdCallback?.reportAdImpression()
}

override fun onAdClicked() {
rewardedAdCallback?.reportAdClicked()
}

override fun onAdOpened() {
rewardedAdCallback?.onAdOpened()
}

override fun onAdClosed() {
rewardedAdCallback?.onAdClosed()
}

companion object {
Expand All @@ -45,12 +96,22 @@ private constructor(
mediationAdLoadCallback:
MediationAdLoadCallback<MediationRewardedAd, MediationRewardedAdCallback>,
): Result<BigoRewardedAd> {
val context = mediationRewardedAdConfiguration.context
val serverParameters = mediationRewardedAdConfiguration.serverParameters
val bidResponse = mediationRewardedAdConfiguration.bidResponse
val slotId = serverParameters.getString(SLOT_ID_KEY)

// TODO: Implement necessary initialization steps.
if (slotId.isNullOrEmpty()) {
val gmaAdError =
BigoUtils.getGmaAdError(
ERROR_CODE_MISSING_SLOT_ID,
ERROR_MSG_MISSING_SLOT_ID,
ADAPTER_ERROR_DOMAIN,
)
mediationAdLoadCallback.onFailure(gmaAdError)
return Result.failure(IllegalArgumentException(gmaAdError.toString()))
}

return Result.success(BigoRewardedAd(context, mediationAdLoadCallback))
return Result.success(BigoRewardedAd(mediationAdLoadCallback, bidResponse, slotId))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.google.ads.mediation.adaptertestkit.assertGetSdkVersion
import com.google.ads.mediation.adaptertestkit.assertGetVersionInfo
import com.google.ads.mediation.adaptertestkit.createMediationConfiguration
import com.google.ads.mediation.adaptertestkit.createMediationInterstitialAdConfiguration
import com.google.ads.mediation.adaptertestkit.createMediationRewardedAdConfiguration
import com.google.ads.mediation.bigo.BigoMediationAdapter.Companion.ADAPTER_ERROR_DOMAIN
import com.google.ads.mediation.bigo.BigoMediationAdapter.Companion.APP_ID_KEY
import com.google.ads.mediation.bigo.BigoMediationAdapter.Companion.ERROR_CODE_MISSING_SLOT_ID
Expand All @@ -39,6 +40,8 @@ import com.google.android.gms.ads.mediation.MediationAdLoadCallback
import com.google.android.gms.ads.mediation.MediationConfiguration
import com.google.android.gms.ads.mediation.MediationInterstitialAd
import com.google.android.gms.ads.mediation.MediationInterstitialAdCallback
import com.google.android.gms.ads.mediation.MediationRewardedAd
import com.google.android.gms.ads.mediation.MediationRewardedAdCallback
import com.google.android.gms.ads.mediation.rtb.RtbSignalData
import com.google.android.gms.ads.mediation.rtb.SignalCallbacks
import org.junit.After
Expand Down Expand Up @@ -248,4 +251,20 @@ class BigoMediationAdapterTest {

// endregion

// region Rewarded tests
@Test
fun loadRtbRewardedAd_withEmptySlotId_invokesOnFailure() {
val adConfiguration = createMediationRewardedAdConfiguration(context)
val mockRewardedAdLoadCallback =
mock<MediationAdLoadCallback<MediationRewardedAd, MediationRewardedAdCallback>>()
val expectedAdError =
AdError(ERROR_CODE_MISSING_SLOT_ID, ERROR_MSG_MISSING_SLOT_ID, ADAPTER_ERROR_DOMAIN)

adapter.loadRtbRewardedAd(adConfiguration, mockRewardedAdLoadCallback)

verify(mockRewardedAdLoadCallback).onFailure(argThat(AdErrorMatcher(expectedAdError)))
}

// endregion

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.ads.mediation.bigo

import android.content.Context
import androidx.core.os.bundleOf
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.ads.mediation.adaptertestkit.AdErrorMatcher
import com.google.ads.mediation.adaptertestkit.AdapterTestKitConstants.TEST_BID_RESPONSE
import com.google.ads.mediation.adaptertestkit.AdapterTestKitConstants.TEST_WATERMARK
import com.google.ads.mediation.adaptertestkit.createMediationRewardedAdConfiguration
import com.google.ads.mediation.bigo.BigoMediationAdapter.Companion.SDK_ERROR_DOMAIN
import com.google.ads.mediation.bigo.BigoMediationAdapter.Companion.SLOT_ID_KEY
import com.google.android.gms.ads.mediation.MediationAdLoadCallback
import com.google.android.gms.ads.mediation.MediationRewardedAd
import com.google.android.gms.ads.mediation.MediationRewardedAdCallback
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.argThat
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.inOrder
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import sg.bigo.ads.api.AdError
import sg.bigo.ads.api.RewardVideoAd
import sg.bigo.ads.api.RewardVideoAdRequest

@RunWith(AndroidJUnit4::class)
class BigoRewardedAdTest {
// Subject of testing
private lateinit var bigoRewardedAd: BigoRewardedAd

private val context = ApplicationProvider.getApplicationContext<Context>()
private val mockRewardedAdCallback: MediationRewardedAdCallback = mock()
private val mockAdLoadCallback:
MediationAdLoadCallback<MediationRewardedAd, MediationRewardedAdCallback> =
mock {
on { onSuccess(any()) } doReturn mockRewardedAdCallback
}
private val mockRewardVideoAdRequest = mock<RewardVideoAdRequest>()
private val mockRewardVideoAdLoader = mock<BigoRewardVideoAdLoaderWrapper>()
private var mockBigoFactory =
mock<SdkFactory> {
on { createRewardVideoAdRequest(eq(TEST_BID_RESPONSE), eq(TEST_SLOT_ID)) } doReturn
mockRewardVideoAdRequest
on { createRewardVideoAdLoader() } doReturn mockRewardVideoAdLoader
}

@Before
fun setUp() {
val serverParams = bundleOf(SLOT_ID_KEY to TEST_SLOT_ID)
val adConfiguration =
createMediationRewardedAdConfiguration(
context = context,
bidResponse = TEST_BID_RESPONSE,
serverParameters = serverParams,
watermark = TEST_WATERMARK,
)
BigoFactory.delegate = mockBigoFactory
BigoRewardedAd.newInstance(adConfiguration, mockAdLoadCallback).onSuccess {
bigoRewardedAd = it
}
}

@Test
fun loadAd_invokesWrapperInitializeAdLoaderAndLoadAd() {
bigoRewardedAd.loadAd()

inOrder(mockRewardVideoAdLoader) {
verify(mockRewardVideoAdLoader).initializeAdLoader(bigoRewardedAd)
verify(mockRewardVideoAdLoader).loadAd(mockRewardVideoAdRequest)
}
}

@Test
fun onAdLoaded_setsListenerAndInvokesOnSuccess() {
val mockRewardVideoAd = mock<RewardVideoAd>()

bigoRewardedAd.onAdLoaded(mockRewardVideoAd)

verify(mockRewardVideoAd).setAdInteractionListener(bigoRewardedAd)
verify(mockAdLoadCallback).onSuccess(bigoRewardedAd)
}

@Test
fun onError_invokesOnFailure() {
val expectedAdError = BigoUtils.getGmaAdError(TEST_ERROR_CODE, TEST_ERROR_MSG, SDK_ERROR_DOMAIN)

bigoRewardedAd.onError(AdError(TEST_ERROR_CODE, TEST_ERROR_MSG))

verify(mockAdLoadCallback).onFailure(argThat(AdErrorMatcher(expectedAdError)))
}

@Test
fun showAd_invokesShow() {
val mockRewardVideoAd = mock<RewardVideoAd>()
bigoRewardedAd.onAdLoaded(mockRewardVideoAd)

bigoRewardedAd.showAd(context)

verify(mockRewardVideoAd).show()
}

@Test
fun onAdRewarded_invokesOnUserEarnedReward() {
bigoRewardedAd.onAdLoaded(mock())

bigoRewardedAd.onAdRewarded()

verify(mockRewardedAdCallback).onUserEarnedReward()
}

@Test
fun onAdError_invokesOnAdFailedToShow() {
val expectedAdError = BigoUtils.getGmaAdError(TEST_ERROR_CODE, TEST_ERROR_MSG, SDK_ERROR_DOMAIN)
bigoRewardedAd.onAdLoaded(mock())

bigoRewardedAd.onAdError(AdError(TEST_ERROR_CODE, TEST_ERROR_MSG))

verify(mockRewardedAdCallback).onAdFailedToShow(argThat(AdErrorMatcher(expectedAdError)))
}

@Test
fun onAdImpression_invokesReportAdImpression() {
bigoRewardedAd.onAdLoaded(mock())

bigoRewardedAd.onAdImpression()

verify(mockRewardedAdCallback).reportAdImpression()
}

@Test
fun onAdClicked_invokesReportAdClicked() {
bigoRewardedAd.onAdLoaded(mock())

bigoRewardedAd.onAdClicked()

verify(mockRewardedAdCallback).reportAdClicked()
}

@Test
fun onAdOpened_invokesOnAdOpened() {
bigoRewardedAd.onAdLoaded(mock())

bigoRewardedAd.onAdOpened()

verify(mockRewardedAdCallback).onAdOpened()
}

@Test
fun onAdClosed_invokesOnAdClosed() {
bigoRewardedAd.onAdLoaded(mock())

bigoRewardedAd.onAdClosed()

verify(mockRewardedAdCallback).onAdClosed()
}

private companion object {
const val TEST_SLOT_ID = "testSlotId"
const val TEST_ERROR_CODE = 123
const val TEST_ERROR_MSG = "testError"
}
}
Loading