From b32a318c53001351e50ac65aec119132bca9ced9 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega Date: Fri, 17 Nov 2023 17:31:13 +0100 Subject: [PATCH] Create a BaseBillingUseCaseTest (#1486) As suggested in https://github.com/RevenueCat/purchases-android/pull/1448#pullrequestreview-1736243570 I created a BaseBillingUseCaseTest to remove some duplicated stuff in the UseCase tests --- .../google/usecase/BaseBillingUseCaseTest.kt | 103 ++++++++++++ .../usecase/GetBillingConfigUseCaseTest.kt | 66 +------- .../usecase/QueryProductDetailsUseCaseTest.kt | 145 +---------------- .../QueryPurchaseHistoryUseCaseTest.kt | 154 +----------------- 4 files changed, 115 insertions(+), 353 deletions(-) create mode 100644 purchases/src/test/java/com/revenuecat/purchases/google/usecase/BaseBillingUseCaseTest.kt diff --git a/purchases/src/test/java/com/revenuecat/purchases/google/usecase/BaseBillingUseCaseTest.kt b/purchases/src/test/java/com/revenuecat/purchases/google/usecase/BaseBillingUseCaseTest.kt new file mode 100644 index 0000000000..70fcf099f9 --- /dev/null +++ b/purchases/src/test/java/com/revenuecat/purchases/google/usecase/BaseBillingUseCaseTest.kt @@ -0,0 +1,103 @@ +package com.revenuecat.purchases.google.usecase + +import android.os.Handler +import com.android.billingclient.api.BillingClient +import com.android.billingclient.api.BillingClientStateListener +import com.android.billingclient.api.BillingResult +import com.android.billingclient.api.PurchasesUpdatedListener +import com.revenuecat.purchases.common.BillingAbstract +import com.revenuecat.purchases.common.DateProvider +import com.revenuecat.purchases.common.caching.DeviceCache +import com.revenuecat.purchases.common.diagnostics.DiagnosticsTracker +import com.revenuecat.purchases.google.BillingWrapper +import com.revenuecat.purchases.utils.MockHandlerFactory +import io.mockk.Runs +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.slot +import org.junit.After +import org.junit.Before +import java.util.Date + +internal open class BaseBillingUseCaseTest { + private var onConnectedCalled: Boolean = false + + private var mockPurchasesListener: BillingAbstract.PurchasesUpdatedListener = mockk() + + protected lateinit var handler: Handler + + protected lateinit var wrapper: BillingWrapper + + protected var mockClient: BillingClient = mockk() + protected var mockDeviceCache: DeviceCache = mockk() + protected var mockDiagnosticsTracker: DiagnosticsTracker = mockk() + protected var mockDateProvider: DateProvider = mockk() + protected val billingClientOKResult = BillingClient.BillingResponseCode.OK.buildResult() + + @Before + open fun setup() { + handler = MockHandlerFactory.createMockHandler() + + mockDiagnosticsTracker() + every { mockDateProvider.now } returns Date(1676379370000) // Tuesday, February 14, 2023 12:56:10 PM GMT + + val mockClientFactory: BillingWrapper.ClientFactory = mockk() + + val listenerSlot = slot() + every { + mockClientFactory.buildClient(capture(listenerSlot)) + } answers { + mockClient + } + + val billingClientStateListenerSlot = slot() + every { + mockClient.startConnection(capture(billingClientStateListenerSlot)) + } just Runs + + every { + mockClient.endConnection() + } just Runs + + every { + mockClient.isReady + } returns false andThen true + + wrapper = BillingWrapper(mockClientFactory, handler, mockDeviceCache, mockDiagnosticsTracker, mockDateProvider) + wrapper.purchasesUpdatedListener = mockPurchasesListener + wrapper.startConnectionOnMainThread() + onConnectedCalled = false + wrapper.stateListener = object : BillingAbstract.StateListener { + override fun onConnected() { + onConnectedCalled = true + } + } + } + + @After + fun tearDown() { + clearAllMocks() + } + + protected fun Int.buildResult(): BillingResult { + return BillingResult.newBuilder().setResponseCode(this).build() + } + + private fun mockDiagnosticsTracker() { + every { + mockDiagnosticsTracker.trackGoogleQueryProductDetailsRequest(any(), any(), any(), any()) + } just Runs + every { + mockDiagnosticsTracker.trackGoogleQueryPurchasesRequest(any(), any(), any(), any()) + } just Runs + every { + mockDiagnosticsTracker.trackGoogleQueryPurchaseHistoryRequest(any(), any(), any(), any()) + } just Runs + every { + mockDiagnosticsTracker.trackProductDetailsNotSupported(any(), any()) + } just Runs + } + +} \ No newline at end of file diff --git a/purchases/src/test/java/com/revenuecat/purchases/google/usecase/GetBillingConfigUseCaseTest.kt b/purchases/src/test/java/com/revenuecat/purchases/google/usecase/GetBillingConfigUseCaseTest.kt index 0d0f8a0b60..02b2da128e 100644 --- a/purchases/src/test/java/com/revenuecat/purchases/google/usecase/GetBillingConfigUseCaseTest.kt +++ b/purchases/src/test/java/com/revenuecat/purchases/google/usecase/GetBillingConfigUseCaseTest.kt @@ -1,28 +1,19 @@ package com.revenuecat.purchases.google.usecase -import android.os.Handler import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.billingclient.api.BillingClient import com.android.billingclient.api.BillingClient.BillingResponseCode -import com.android.billingclient.api.BillingClientStateListener import com.android.billingclient.api.BillingConfig import com.android.billingclient.api.BillingConfigResponseListener import com.android.billingclient.api.BillingResult -import com.android.billingclient.api.PurchasesUpdatedListener import com.revenuecat.purchases.PurchasesError import com.revenuecat.purchases.PurchasesErrorCode -import com.revenuecat.purchases.common.caching.DeviceCache -import com.revenuecat.purchases.google.BillingWrapper -import com.revenuecat.purchases.utils.MockHandlerFactory import io.mockk.Runs -import io.mockk.clearAllMocks import io.mockk.every import io.mockk.just import io.mockk.mockk import io.mockk.slot import io.mockk.verify import org.assertj.core.api.Assertions.assertThat -import org.junit.After import org.junit.Assert.fail import org.junit.Before import org.junit.Test @@ -31,7 +22,7 @@ import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) @Config(manifest = Config.NONE) -class GetBillingConfigUseCaseTest { +internal class GetBillingConfigUseCaseTest: BaseBillingUseCaseTest() { private val expectedCountryCode = "JP" @@ -39,55 +30,10 @@ class GetBillingConfigUseCaseTest { every { countryCode } returns expectedCountryCode } - private var mockClientFactory: BillingWrapper.ClientFactory = mockk() - private var mockClient: BillingClient = mockk() - private var deviceCache: DeviceCache = mockk() - private var purchasesUpdatedListener: PurchasesUpdatedListener? = null - private var billingClientStateListener: BillingClientStateListener? = null - - private lateinit var handler: Handler - - private lateinit var wrapper: BillingWrapper - @Before - fun setup() { - handler = MockHandlerFactory.createMockHandler() - purchasesUpdatedListener = null - billingClientStateListener = null - - every { deviceCache.setStorefront(expectedCountryCode) } just Runs - - val listenerSlot = slot() - every { - mockClientFactory.buildClient(capture(listenerSlot)) - } answers { - purchasesUpdatedListener = listenerSlot.captured - mockClient - } - - val billingClientStateListenerSlot = slot() - every { - mockClient.startConnection(capture(billingClientStateListenerSlot)) - } answers { - billingClientStateListener = billingClientStateListenerSlot.captured - } - - every { - mockClient.endConnection() - } just Runs - - every { - mockClient.isReady - } returns false andThen true - - wrapper = BillingWrapper(mockClientFactory, handler, deviceCache, mockk(), mockk()) - wrapper.purchasesUpdatedListener = mockk() - wrapper.startConnectionOnMainThread() - } - - @After - fun tearDown() { - clearAllMocks() + override fun setup() { + super.setup() + every { mockDeviceCache.setStorefront(expectedCountryCode) } just Runs } @Test @@ -108,7 +54,7 @@ class GetBillingConfigUseCaseTest { onSuccess = { }, onError = { fail("Should succeed") } ) - verify(exactly = 1) { deviceCache.setStorefront(expectedCountryCode) } + verify(exactly = 1) { mockDeviceCache.setStorefront(expectedCountryCode) } } @Test @@ -150,7 +96,7 @@ class GetBillingConfigUseCaseTest { onSuccess = { fail("Should error") }, onError = { } ) - verify(exactly = 0) { deviceCache.setStorefront(expectedCountryCode) } + verify(exactly = 0) { mockDeviceCache.setStorefront(expectedCountryCode) } } private fun mockGetBillingConfig( diff --git a/purchases/src/test/java/com/revenuecat/purchases/google/usecase/QueryProductDetailsUseCaseTest.kt b/purchases/src/test/java/com/revenuecat/purchases/google/usecase/QueryProductDetailsUseCaseTest.kt index 6be56add8a..2a16197cd3 100644 --- a/purchases/src/test/java/com/revenuecat/purchases/google/usecase/QueryProductDetailsUseCaseTest.kt +++ b/purchases/src/test/java/com/revenuecat/purchases/google/usecase/QueryProductDetailsUseCaseTest.kt @@ -1,151 +1,45 @@ package com.revenuecat.purchases.google.usecase -import android.app.Activity -import android.content.Intent -import android.os.Handler import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.billingclient.api.AcknowledgePurchaseParams -import com.android.billingclient.api.AcknowledgePurchaseResponseListener import com.android.billingclient.api.BillingClient -import com.android.billingclient.api.BillingClientStateListener -import com.android.billingclient.api.BillingResult -import com.android.billingclient.api.ConsumeParams -import com.android.billingclient.api.ConsumeResponseListener import com.android.billingclient.api.ProductDetails import com.android.billingclient.api.ProductDetailsResponseListener -import com.android.billingclient.api.PurchasesUpdatedListener import com.android.billingclient.api.QueryProductDetailsParams import com.revenuecat.purchases.ProductType import com.revenuecat.purchases.PurchasesError import com.revenuecat.purchases.PurchasesErrorCode -import com.revenuecat.purchases.common.BillingAbstract -import com.revenuecat.purchases.common.DateProvider -import com.revenuecat.purchases.common.caching.DeviceCache -import com.revenuecat.purchases.common.diagnostics.DiagnosticsTracker -import com.revenuecat.purchases.google.BillingWrapper import com.revenuecat.purchases.google.productId import com.revenuecat.purchases.google.productList import com.revenuecat.purchases.google.productType import com.revenuecat.purchases.models.StoreProduct -import com.revenuecat.purchases.utils.MockHandlerFactory import com.revenuecat.purchases.utils.mockProductDetails import io.mockk.Runs -import io.mockk.clearAllMocks import io.mockk.every import io.mockk.just -import io.mockk.mockk import io.mockk.slot import io.mockk.verify import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.fail import org.assertj.core.api.AssertionsForClassTypes -import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config -import java.util.Date import java.util.concurrent.CountDownLatch import java.util.concurrent.atomic.AtomicInteger @RunWith(AndroidJUnit4::class) @Config(manifest = Config.NONE) -class QueryProductDetailsUseCaseTest { - - private var onConnectedCalled: Boolean = false - private var mockClientFactory: BillingWrapper.ClientFactory = mockk() - private var mockClient: BillingClient = mockk() - private var purchasesUpdatedListener: PurchasesUpdatedListener? = null - private var billingClientStateListener: BillingClientStateListener? = null - private var mockDeviceCache: DeviceCache = mockk() - private var mockDiagnosticsTracker: DiagnosticsTracker = mockk() - private var mockDateProvider: DateProvider = mockk() - - private var mockPurchasesListener: BillingAbstract.PurchasesUpdatedListener = mockk() - - private var capturedAcknowledgeResponseListener = slot() - private var capturedAcknowledgePurchaseParams = slot() - private var capturedConsumeResponseListener = slot() - private var capturedConsumeParams = slot() - - private lateinit var handler: Handler - private lateinit var wrapper: BillingWrapper +internal class QueryProductDetailsUseCaseTest: BaseBillingUseCaseTest() { private lateinit var mockDetailsList: List private var storeProducts: List? = null - private val billingClientOKResult = BillingClient.BillingResponseCode.OK.buildResult() - private var mockActivity = mockk() - @Before - fun setup() { - handler = MockHandlerFactory.createMockHandler() - - storeProducts = null - purchasesUpdatedListener = null - billingClientStateListener = null - - mockDiagnosticsTracker() - every { mockDateProvider.now } returns Date(1676379370000) // Tuesday, February 14, 2023 12:56:10 PM GMT - - val listenerSlot = slot() - every { - mockClientFactory.buildClient(capture(listenerSlot)) - } answers { - purchasesUpdatedListener = listenerSlot.captured - mockClient - } - - val billingClientStateListenerSlot = slot() - every { - mockClient.startConnection(capture(billingClientStateListenerSlot)) - } answers { - billingClientStateListener = billingClientStateListenerSlot.captured - } - - every { - mockClient.endConnection() - } just Runs - - every { - mockClient.acknowledgePurchase( - capture(capturedAcknowledgePurchaseParams), - capture(capturedAcknowledgeResponseListener) - ) - } just Runs - - mockConsumeAsync(billingClientOKResult) - - every { - mockClient.isReady - } returns false andThen true - - val featureSlot = slot() - every { - mockClient.isFeatureSupported(capture(featureSlot)) - } returns billingClientOKResult - + override fun setup() { + super.setup() mockDetailsList = listOf(mockProductDetails()) - - wrapper = BillingWrapper(mockClientFactory, handler, mockDeviceCache, mockDiagnosticsTracker, mockDateProvider) - wrapper.purchasesUpdatedListener = mockPurchasesListener - wrapper.startConnectionOnMainThread() - onConnectedCalled = false - wrapper.stateListener = object : BillingAbstract.StateListener { - override fun onConnected() { - onConnectedCalled = true - } - } - - every { - mockActivity.intent - } returns Intent() - } - - @After - fun tearDown() { - clearAllMocks() } @Test @@ -162,7 +56,7 @@ class QueryProductDetailsUseCaseTest { }, { AssertionsForClassTypes.fail("shouldn't be an error") }) - wrapper.onBillingSetupFinished(billingClientOKResult) + assertThat(receivedList).isNotNull assertThat(receivedList!!.size).isZero } @@ -563,7 +457,7 @@ class QueryProductDetailsUseCaseTest { assertThat(receivedError!!.code).isEqualTo(PurchasesErrorCode.ProductNotAvailableForPurchaseError) } - // endregion + // endregion retries private fun mockEmptyProductDetailsResponse() { val slot = slot() @@ -577,33 +471,4 @@ class QueryProductDetailsUseCaseTest { } } - private fun Int.buildResult(): BillingResult { - return BillingResult.newBuilder().setResponseCode(this).build() - } - - private fun mockConsumeAsync(billingResult: BillingResult) { - every { - mockClient.consumeAsync(capture(capturedConsumeParams), capture(capturedConsumeResponseListener)) - } answers { - capturedConsumeResponseListener.captured.onConsumeResponse( - billingResult, - capturedConsumeParams.captured.purchaseToken - ) - } - } - - private fun mockDiagnosticsTracker() { - every { - mockDiagnosticsTracker.trackGoogleQueryProductDetailsRequest(any(), any(), any(), any()) - } just Runs - every { - mockDiagnosticsTracker.trackGoogleQueryPurchasesRequest(any(), any(), any(), any()) - } just Runs - every { - mockDiagnosticsTracker.trackGoogleQueryPurchaseHistoryRequest(any(), any(), any(), any()) - } just Runs - every { - mockDiagnosticsTracker.trackProductDetailsNotSupported(any(), any()) - } just Runs - } } \ No newline at end of file diff --git a/purchases/src/test/java/com/revenuecat/purchases/google/usecase/QueryPurchaseHistoryUseCaseTest.kt b/purchases/src/test/java/com/revenuecat/purchases/google/usecase/QueryPurchaseHistoryUseCaseTest.kt index d9eb365a65..7d165dbd1e 100644 --- a/purchases/src/test/java/com/revenuecat/purchases/google/usecase/QueryPurchaseHistoryUseCaseTest.kt +++ b/purchases/src/test/java/com/revenuecat/purchases/google/usecase/QueryPurchaseHistoryUseCaseTest.kt @@ -1,47 +1,26 @@ package com.revenuecat.purchases.google.usecase -import android.app.Activity -import android.content.Intent -import android.os.Handler import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.billingclient.api.AcknowledgePurchaseParams -import com.android.billingclient.api.AcknowledgePurchaseResponseListener import com.android.billingclient.api.BillingClient -import com.android.billingclient.api.BillingClientStateListener import com.android.billingclient.api.BillingResult import com.android.billingclient.api.ConsumeParams import com.android.billingclient.api.ConsumeResponseListener -import com.android.billingclient.api.ProductDetails import com.android.billingclient.api.PurchaseHistoryRecord import com.android.billingclient.api.PurchaseHistoryResponseListener -import com.android.billingclient.api.PurchasesUpdatedListener import com.android.billingclient.api.QueryPurchaseHistoryParams import com.revenuecat.purchases.ProductType import com.revenuecat.purchases.PurchasesError import com.revenuecat.purchases.PurchasesErrorCode -import com.revenuecat.purchases.common.BillingAbstract -import com.revenuecat.purchases.common.DateProvider -import com.revenuecat.purchases.common.caching.DeviceCache -import com.revenuecat.purchases.common.diagnostics.DiagnosticsTracker import com.revenuecat.purchases.common.firstSku -import com.revenuecat.purchases.google.BillingWrapper import com.revenuecat.purchases.google.toGoogleProductType -import com.revenuecat.purchases.models.StoreProduct -import com.revenuecat.purchases.utils.MockHandlerFactory -import com.revenuecat.purchases.utils.mockProductDetails import com.revenuecat.purchases.utils.mockQueryPurchaseHistory import com.revenuecat.purchases.utils.verifyQueryPurchaseHistoryCalledWithType -import io.mockk.Runs -import io.mockk.clearAllMocks import io.mockk.every -import io.mockk.just import io.mockk.mockk import io.mockk.slot import io.mockk.verify import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.fail -import org.junit.After -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config @@ -52,113 +31,19 @@ import kotlin.time.Duration.Companion.milliseconds @RunWith(AndroidJUnit4::class) @Config(manifest = Config.NONE) -class QueryPurchaseHistoryUseCaseTest { +internal class QueryPurchaseHistoryUseCaseTest: BaseBillingUseCaseTest() { private companion object { const val timestamp0 = 1676379370000 // Tuesday, February 14, 2023 12:56:10.000 PM GMT const val timestamp123 = 1676379370123 // Tuesday, February 14, 2023 12:56:10.123 PM GMT } - private var onConnectedCalled: Boolean = false - private var mockClientFactory: BillingWrapper.ClientFactory = mockk() - private var mockClient: BillingClient = mockk() - private var purchasesUpdatedListener: PurchasesUpdatedListener? = null - private var billingClientStateListener: BillingClientStateListener? = null - - private var mockDeviceCache: DeviceCache = mockk() - private var mockDiagnosticsTracker: DiagnosticsTracker = mockk() - private var mockDateProvider: DateProvider = mockk() - - private var mockPurchasesListener: BillingAbstract.PurchasesUpdatedListener = mockk() - - private var capturedAcknowledgeResponseListener = slot() - private var capturedAcknowledgePurchaseParams = slot() private var capturedConsumeResponseListener = slot() private var capturedConsumeParams = slot() - private lateinit var handler: Handler - private lateinit var wrapper: BillingWrapper - - private lateinit var mockDetailsList: List - - private var storeProducts: List? = null - - private val billingClientOKResult = BillingClient.BillingResponseCode.OK.buildResult() - private var mockActivity = mockk() - private val subsGoogleProductType = ProductType.SUBS.toGoogleProductType()!! private val inAppGoogleProductType = ProductType.INAPP.toGoogleProductType()!! - @Before - fun setup() { - handler = MockHandlerFactory.createMockHandler() - - storeProducts = null - purchasesUpdatedListener = null - billingClientStateListener = null - - mockDiagnosticsTracker() - every { mockDateProvider.now } returns Date(1676379370000) // Tuesday, February 14, 2023 12:56:10 PM GMT - - val listenerSlot = slot() - every { - mockClientFactory.buildClient(capture(listenerSlot)) - } answers { - purchasesUpdatedListener = listenerSlot.captured - mockClient - } - - val billingClientStateListenerSlot = slot() - every { - mockClient.startConnection(capture(billingClientStateListenerSlot)) - } answers { - billingClientStateListener = billingClientStateListenerSlot.captured - } - - every { - mockClient.endConnection() - } just Runs - - every { - mockClient.acknowledgePurchase( - capture(capturedAcknowledgePurchaseParams), - capture(capturedAcknowledgeResponseListener) - ) - } just Runs - - mockConsumeAsync(billingClientOKResult) - - every { - mockClient.isReady - } returns false andThen true - - val featureSlot = slot() - every { - mockClient.isFeatureSupported(capture(featureSlot)) - } returns billingClientOKResult - - mockDetailsList = listOf(mockProductDetails()) - - wrapper = BillingWrapper(mockClientFactory, handler, mockDeviceCache, mockDiagnosticsTracker, mockDateProvider) - wrapper.purchasesUpdatedListener = mockPurchasesListener - wrapper.startConnectionOnMainThread() - onConnectedCalled = false - wrapper.stateListener = object : BillingAbstract.StateListener { - override fun onConnected() { - onConnectedCalled = true - } - } - - every { - mockActivity.intent - } returns Intent() - } - - @After - fun tearDown() { - clearAllMocks() - } - @Test fun `if no listener is set, we fail`() { wrapper.purchasesUpdatedListener = null @@ -180,8 +65,6 @@ class QueryPurchaseHistoryUseCaseTest { @Test fun `queryPurchaseHistoryAsync fails if sent invalid type`() { - billingClientStateListener!!.onBillingSetupFinished(billingClientOKResult) - mockClient.mockQueryPurchaseHistory( billingClientOKResult, emptyList() @@ -266,8 +149,6 @@ class QueryPurchaseHistoryUseCaseTest { @Test fun queryHistoryCallsListenerIfOk() { - billingClientStateListener!!.onBillingSetupFinished(billingClientOKResult) - mockClient.mockQueryPurchaseHistory( billingClientOKResult, emptyList() @@ -288,8 +169,6 @@ class QueryPurchaseHistoryUseCaseTest { @Test fun queryHistoryErrorCalledIfNotOK() { - billingClientStateListener!!.onBillingSetupFinished(billingClientOKResult) - mockClient.mockQueryPurchaseHistory( BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED.buildResult(), emptyList() @@ -312,8 +191,6 @@ class QueryPurchaseHistoryUseCaseTest { @Test fun `queryPurchaseHistoryAsync sets correct type`() { - billingClientStateListener!!.onBillingSetupFinished(billingClientOKResult) - val subsBuilder = mockClient.mockQueryPurchaseHistory( billingClientOKResult, emptyList() @@ -650,33 +527,4 @@ class QueryPurchaseHistoryUseCaseTest { // endregion retries - private fun Int.buildResult(): BillingResult { - return BillingResult.newBuilder().setResponseCode(this).build() - } - - private fun mockConsumeAsync(billingResult: BillingResult) { - every { - mockClient.consumeAsync(capture(capturedConsumeParams), capture(capturedConsumeResponseListener)) - } answers { - capturedConsumeResponseListener.captured.onConsumeResponse( - billingResult, - capturedConsumeParams.captured.purchaseToken - ) - } - } - - private fun mockDiagnosticsTracker() { - every { - mockDiagnosticsTracker.trackGoogleQueryProductDetailsRequest(any(), any(), any(), any()) - } just Runs - every { - mockDiagnosticsTracker.trackGoogleQueryPurchasesRequest(any(), any(), any(), any()) - } just Runs - every { - mockDiagnosticsTracker.trackGoogleQueryPurchaseHistoryRequest(any(), any(), any(), any()) - } just Runs - every { - mockDiagnosticsTracker.trackProductDetailsNotSupported(any(), any()) - } just Runs - } } \ No newline at end of file