Commit f0295048 authored by Tiger Oakes's avatar Tiger Oakes Committed by Emily Kager
Browse files

Remove Mockito

parent 4fac1959
......@@ -593,8 +593,6 @@ dependencies {
testImplementation 'org.apache.maven:maven-ant-tasks:2.1.3'
implementation Deps.mozilla_support_rusthttp
testImplementation Deps.mockito_core
androidTestImplementation Deps.mockito_android
testImplementation Deps.mockk
// For the initial release of Glean 19, we require consumer applications to
......
......@@ -32,7 +32,7 @@ class IntentReceiverActivity : Activity() {
// The intent property is nullable, but the rest of the code below
// assumes it is not. If it's null, then we make a new one and open
// the HomeActivity.
val intent = intent?.let { Intent(intent) } ?: Intent()
val intent = intent?.let { Intent(it) } ?: Intent()
intent.stripUnwantedFlags()
processIntent(intent)
}
......
......@@ -18,7 +18,7 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import mozilla.components.concept.sync.DeviceCapability
import mozilla.components.feature.share.RecentAppsStorage
......@@ -38,6 +38,8 @@ class ShareViewModel(application: Application) : AndroidViewModel(application) {
private val fxaAccountManager = application.components.backgroundServices.accountManager
@VisibleForTesting
internal var recentAppsStorage = RecentAppsStorage(application.applicationContext)
@VisibleForTesting
internal var ioDispatcher = Dispatchers.IO
private val devicesListLiveData = MutableLiveData<List<SyncShareOption>>(emptyList())
private val appsListLiveData = MutableLiveData<List<AppShareOption>>(emptyList())
......@@ -49,7 +51,7 @@ class ShareViewModel(application: Application) : AndroidViewModel(application) {
override fun onAvailable(network: Network?) = reloadDevices(network)
private fun reloadDevices(network: Network?) {
viewModelScope.launch(IO) {
viewModelScope.launch(ioDispatcher) {
fxaAccountManager.authenticatedAccount()
?.deviceConstellation()
?.refreshDevicesAsync()
......@@ -83,7 +85,7 @@ class ShareViewModel(application: Application) : AndroidViewModel(application) {
connectivityManager?.registerNetworkCallback(networkRequest, networkCallback)
// Start preparing the data as soon as we have a valid Context
viewModelScope.launch(IO) {
viewModelScope.launch(ioDispatcher) {
val shareIntent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
flags = Intent.FLAG_ACTIVITY_NEW_TASK
......@@ -98,7 +100,7 @@ class ShareViewModel(application: Application) : AndroidViewModel(application) {
appsListLiveData.postValue(apps)
}
viewModelScope.launch(IO) {
viewModelScope.launch(ioDispatcher) {
val devices = buildDeviceList(fxaAccountManager)
devicesListLiveData.postValue(devices)
}
......
......@@ -4,42 +4,63 @@
package org.mozilla.fenix
import android.app.Activity
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import mozilla.components.support.test.robolectric.testContext
import mozilla.components.feature.intent.processing.IntentProcessor
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mozilla.fenix.components.IntentProcessors
import org.mozilla.fenix.customtabs.ExternalAppBrowserActivity
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.shortcut.NewTabShortcutIntentProcessor
import org.mozilla.fenix.utils.Settings
import org.robolectric.Robolectric
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.robolectric.Shadows.shadowOf
@ExperimentalCoroutinesApi
@RunWith(FenixRobolectricTestRunner::class)
class IntentReceiverActivityTest {
private lateinit var settings: Settings
private lateinit var intentProcessors: IntentProcessors
@Before
fun setup() {
settings = mockk()
intentProcessors = mockk()
every { settings.openLinksInAPrivateTab } returns false
every { intentProcessors.intentProcessor } returns mockIntentProcessor()
every { intentProcessors.privateIntentProcessor } returns mockIntentProcessor()
every { intentProcessors.customTabIntentProcessor } returns mockIntentProcessor()
every { intentProcessors.privateCustomTabIntentProcessor } returns mockIntentProcessor()
every { intentProcessors.externalAppIntentProcessors } returns emptyList()
every { intentProcessors.fennecPageShortcutIntentProcessor } returns mockIntentProcessor()
every { intentProcessors.migrationIntentProcessor } returns mockIntentProcessor()
coEvery { intentProcessors.intentProcessor.process(any()) } returns true
}
@Test
fun `process intent with flag launched from history`() = runBlockingTest {
testContext.settings().openLinksInAPrivateTab = false
val intent = Intent()
intent.flags = FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
`when`(testContext.components.intentProcessors.migrationIntentProcessor.process(intent)).thenReturn(false)
`when`(testContext.components.intentProcessors.intentProcessor.process(intent)).thenReturn(true)
`when`(testContext.components.intentProcessors.customTabIntentProcessor.process(intent)).thenReturn(false)
`when`(testContext.components.intentProcessors.fennecPageShortcutIntentProcessor.process(intent)).thenReturn(false)
val activity = Robolectric.buildActivity(IntentReceiverActivity::class.java, intent).get()
attachMocks(activity)
activity.processIntent(intent)
val shadow = shadowOf(activity)
......@@ -51,16 +72,13 @@ class IntentReceiverActivityTest {
@Test
fun `process intent with action OPEN_PRIVATE_TAB`() = runBlockingTest {
testContext.settings().openLinksInAPrivateTab = false
val intent = Intent()
intent.action = NewTabShortcutIntentProcessor.ACTION_OPEN_PRIVATE_TAB
`when`(testContext.components.intentProcessors.migrationIntentProcessor.process(intent)).thenReturn(false)
`when`(testContext.components.intentProcessors.intentProcessor.process(intent)).thenReturn(false)
`when`(testContext.components.intentProcessors.customTabIntentProcessor.process(intent)).thenReturn(false)
`when`(testContext.components.intentProcessors.fennecPageShortcutIntentProcessor.process(intent)).thenReturn(false)
coEvery { intentProcessors.intentProcessor.process(intent) } returns false
coEvery { intentProcessors.customTabIntentProcessor.process(intent) } returns false
val activity = Robolectric.buildActivity(IntentReceiverActivity::class.java, intent).get()
attachMocks(activity)
activity.processIntent(intent)
val shadow = shadowOf(activity)
......@@ -73,16 +91,11 @@ class IntentReceiverActivityTest {
@Test
fun `process intent with action OPEN_TAB`() = runBlockingTest {
testContext.settings().openLinksInAPrivateTab = false
val intent = Intent()
intent.action = NewTabShortcutIntentProcessor.ACTION_OPEN_TAB
`when`(testContext.components.intentProcessors.migrationIntentProcessor.process(intent)).thenReturn(false)
`when`(testContext.components.intentProcessors.intentProcessor.process(intent)).thenReturn(false)
`when`(testContext.components.intentProcessors.customTabIntentProcessor.process(intent)).thenReturn(false)
`when`(testContext.components.intentProcessors.fennecPageShortcutIntentProcessor.process(intent)).thenReturn(false)
val activity = Robolectric.buildActivity(IntentReceiverActivity::class.java, intent).get()
attachMocks(activity)
activity.processIntent(intent)
val shadow = shadowOf(activity)
......@@ -90,19 +103,13 @@ class IntentReceiverActivityTest {
assertEquals(HomeActivity::class.java.name, actualIntent.component?.className)
assertEquals(false, actualIntent.getBooleanExtra(HomeActivity.PRIVATE_BROWSING_MODE, false))
assertEquals(false, actualIntent.getBooleanExtra(HomeActivity.OPEN_TO_BROWSER, true))
}
@Test
fun `process intent starts Activity`() = runBlockingTest {
testContext.settings().openLinksInAPrivateTab = false
val intent = Intent()
`when`(testContext.components.intentProcessors.migrationIntentProcessor.process(intent)).thenReturn(false)
`when`(testContext.components.intentProcessors.intentProcessor.process(intent)).thenReturn(true)
`when`(testContext.components.intentProcessors.customTabIntentProcessor.process(intent)).thenReturn(false)
`when`(testContext.components.intentProcessors.fennecPageShortcutIntentProcessor.process(intent)).thenReturn(false)
val activity = Robolectric.buildActivity(IntentReceiverActivity::class.java, intent).get()
attachMocks(activity)
activity.processIntent(intent)
val shadow = shadowOf(activity)
......@@ -114,57 +121,45 @@ class IntentReceiverActivityTest {
@Test
fun `process intent with launchLinksInPrivateTab set to true`() = runBlockingTest {
testContext.settings().openLinksInAPrivateTab = true
every { settings.openLinksInAPrivateTab } returns true
coEvery { intentProcessors.intentProcessor.process(any()) } returns false
coEvery { intentProcessors.privateIntentProcessor.process(any()) } returns true
val intent = Intent()
`when`(testContext.components.intentProcessors.migrationIntentProcessor.process(intent)).thenReturn(false)
`when`(testContext.components.intentProcessors.privateIntentProcessor.process(intent)).thenReturn(true)
`when`(testContext.components.intentProcessors.privateCustomTabIntentProcessor.process(intent)).thenReturn(false)
`when`(testContext.components.intentProcessors.fennecPageShortcutIntentProcessor.process(intent)).thenReturn(false)
val activity = Robolectric.buildActivity(IntentReceiverActivity::class.java, intent).get()
attachMocks(activity)
activity.processIntent(intent)
// Not using mockk here because process is a suspend function
// and mockito makes this easier to read.
verify(testContext.components.intentProcessors.intentProcessor, never()).process(intent)
verify(testContext.components.intentProcessors.privateIntentProcessor).process(intent)
val normalProcessor = intentProcessors.intentProcessor
coVerify(exactly = 0) { normalProcessor.process(intent) }
coVerify { intentProcessors.privateIntentProcessor.process(intent) }
}
@Test
fun `process intent with launchLinksInPrivateTab set to false`() = runBlockingTest {
testContext.settings().openLinksInAPrivateTab = false
val intent = Intent()
`when`(testContext.components.intentProcessors.migrationIntentProcessor.process(intent)).thenReturn(false)
`when`(testContext.components.intentProcessors.intentProcessor.process(intent)).thenReturn(true)
`when`(testContext.components.intentProcessors.customTabIntentProcessor.process(intent)).thenReturn(false)
`when`(testContext.components.intentProcessors.fennecPageShortcutIntentProcessor.process(intent)).thenReturn(false)
val activity = Robolectric.buildActivity(IntentReceiverActivity::class.java, intent).get()
attachMocks(activity)
activity.processIntent(intent)
// Not using mockk here because process is a suspend function
// and mockito makes this easier to read.
verify(testContext.components.intentProcessors.privateIntentProcessor, never()).process(intent)
verify(testContext.components.intentProcessors.intentProcessor).process(intent)
coVerify(exactly = 0) { intentProcessors.privateIntentProcessor.process(intent) }
coVerify { intentProcessors.intentProcessor.process(intent) }
}
@Test
fun `process custom tab intent`() = runBlockingTest {
testContext.settings().openLinksInAPrivateTab = false
val intent = Intent()
`when`(testContext.components.intentProcessors.migrationIntentProcessor.process(intent)).thenReturn(false)
`when`(testContext.components.intentProcessors.fennecPageShortcutIntentProcessor.process(intent)).thenReturn(false)
`when`(testContext.components.intentProcessors.customTabIntentProcessor.process(intent)).thenReturn(true)
coEvery { intentProcessors.intentProcessor.process(intent) } returns false
coEvery { intentProcessors.customTabIntentProcessor.process(intent) } returns true
val activity = Robolectric.buildActivity(IntentReceiverActivity::class.java, intent).get()
attachMocks(activity)
activity.processIntent(intent)
// Not using mockk here because process is a suspend function
// and mockito makes this easier to read.
verify(testContext.components.intentProcessors.privateIntentProcessor, never()).process(intent)
verify(testContext.components.intentProcessors.customTabIntentProcessor).process(intent)
coVerify(exactly = 0) { intentProcessors.privateCustomTabIntentProcessor.process(intent) }
coVerify { intentProcessors.customTabIntentProcessor.process(intent) }
assertEquals(ExternalAppBrowserActivity::class.java.name, intent.component!!.className)
assertTrue(intent.getBooleanExtra(HomeActivity.OPEN_TO_BROWSER, false))
......@@ -172,22 +167,33 @@ class IntentReceiverActivityTest {
@Test
fun `process private custom tab intent`() = runBlockingTest {
testContext.settings().openLinksInAPrivateTab = true
every { settings.openLinksInAPrivateTab } returns true
val intent = Intent()
`when`(testContext.components.intentProcessors.migrationIntentProcessor.process(intent)).thenReturn(false)
`when`(testContext.components.intentProcessors.privateCustomTabIntentProcessor.process(intent)).thenReturn(true)
`when`(testContext.components.intentProcessors.fennecPageShortcutIntentProcessor.process(intent)).thenReturn(false)
coEvery { intentProcessors.privateCustomTabIntentProcessor.process(intent) } returns true
val activity = Robolectric.buildActivity(IntentReceiverActivity::class.java, intent).get()
attachMocks(activity)
activity.processIntent(intent)
// Not using mockk here because process is a suspend function
// and mockito makes this easier to read.
verify(testContext.components.intentProcessors.intentProcessor, never()).process(intent)
verify(testContext.components.intentProcessors.privateCustomTabIntentProcessor).process(intent)
val normalProcessor = intentProcessors.customTabIntentProcessor
coVerify(exactly = 0) { normalProcessor.process(intent) }
coVerify { intentProcessors.privateCustomTabIntentProcessor.process(intent) }
assertEquals(ExternalAppBrowserActivity::class.java.name, intent.component!!.className)
assertTrue(intent.getBooleanExtra(HomeActivity.OPEN_TO_BROWSER, false))
}
private fun attachMocks(activity: Activity) {
mockkStatic("org.mozilla.fenix.ext.ContextKt")
every { activity.settings() } returns settings
every { activity.components.analytics } returns mockk(relaxed = true)
every { activity.components.intentProcessors } returns intentProcessors
}
private inline fun <reified T : IntentProcessor> mockIntentProcessor(): T {
return mockk {
coEvery { process(any()) } returns false
}
}
}
......@@ -4,27 +4,26 @@
package org.mozilla.fenix.components
import io.mockk.Called
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.runBlocking
import mozilla.components.lib.crash.CrashReporter
import mozilla.components.service.fxa.manager.FxaAccountManager
import mozilla.components.support.test.argumentCaptor
import mozilla.components.support.test.mock
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.Mockito.verify
import kotlin.reflect.KClass
@RunWith(FenixRobolectricTestRunner::class)
class AccountAbnormalitiesTest {
@Test
fun `account manager must be configured`() {
val crashReporter: CrashReporter = mock()
val crashReporter: CrashReporter = mockk()
// no account present
val accountAbnormalities = AccountAbnormalities(testContext, crashReporter)
......@@ -37,7 +36,7 @@ class AccountAbnormalitiesTest {
}
try {
accountAbnormalities.onAuthenticated(mock(), mock())
accountAbnormalities.onAuthenticated(mockk(), mockk())
fail()
} catch (e: IllegalStateException) {
assertEquals("onAuthenticated before account manager was configured", e.message)
......@@ -50,13 +49,13 @@ class AccountAbnormalitiesTest {
assertEquals("onLoggedOut before account manager was configured", e.message)
}
verifyZeroInteractions(crashReporter)
verify { crashReporter wasNot Called }
}
@Test
fun `LogoutWithoutAuth detected`() = runBlocking {
val crashReporter: CrashReporter = mock()
val accountManager: FxaAccountManager = mock()
val crashReporter: CrashReporter = mockk(relaxed = true)
val accountManager: FxaAccountManager = mockk(relaxed = true)
val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext)
accountAbnormalities.accountManagerInitializedAsync(
......@@ -66,13 +65,13 @@ class AccountAbnormalitiesTest {
// Logout action must be preceded by auth.
accountAbnormalities.userRequestedLogout()
assertCaughtException(crashReporter, AbnormalFxaEvent.LogoutWithoutAuth::class)
assertCaughtException<AbnormalFxaEvent.LogoutWithoutAuth>(crashReporter)
}
@Test
fun `OverlappingFxaLogoutRequest detected`() = runBlocking {
val crashReporter: CrashReporter = mock()
val accountManager: FxaAccountManager = mock()
val crashReporter: CrashReporter = mockk(relaxed = true)
val accountManager: FxaAccountManager = mockk(relaxed = true)
val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext)
accountAbnormalities.accountManagerInitializedAsync(
......@@ -80,20 +79,20 @@ class AccountAbnormalitiesTest {
CompletableDeferred(Unit).also { it.complete(Unit) }
).await()
accountAbnormalities.onAuthenticated(mock(), mock())
accountAbnormalities.onAuthenticated(mockk(), mockk())
// So far, so good. A regular logout request while being authenticated.
accountAbnormalities.userRequestedLogout()
verifyZeroInteractions(crashReporter)
verify { crashReporter wasNot Called }
// We never saw a logout callback after previous logout request, so this is an overlapping request.
accountAbnormalities.userRequestedLogout()
assertCaughtException(crashReporter, AbnormalFxaEvent.OverlappingFxaLogoutRequest::class)
assertCaughtException<AbnormalFxaEvent.OverlappingFxaLogoutRequest>(crashReporter)
}
@Test
fun `callback logout abnormalities detected`() = runBlocking {
val crashReporter: CrashReporter = mock()
val accountManager: FxaAccountManager = mock()
val crashReporter: CrashReporter = mockk(relaxed = true)
val accountManager: FxaAccountManager = mockk(relaxed = true)
val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext)
accountAbnormalities.accountManagerInitializedAsync(
......@@ -103,13 +102,13 @@ class AccountAbnormalitiesTest {
// User didn't request this logout.
accountAbnormalities.onLoggedOut()
assertCaughtException(crashReporter, AbnormalFxaEvent.UnexpectedFxaLogout::class)
assertCaughtException<AbnormalFxaEvent.UnexpectedFxaLogout>(crashReporter)
}
@Test
fun `login happy case + disappearing account detected`() = runBlocking {
val crashReporter: CrashReporter = mock()
val accountManager: FxaAccountManager = mock()
val crashReporter: CrashReporter = mockk(relaxed = true)
val accountManager: FxaAccountManager = mockk(relaxed = true)
val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext)
accountAbnormalities.accountManagerInitializedAsync(
......@@ -117,8 +116,9 @@ class AccountAbnormalitiesTest {
CompletableDeferred(Unit).also { it.complete(Unit) }
).await()
accountAbnormalities.onAuthenticated(mock(), mock())
verifyZeroInteractions(crashReporter)
accountAbnormalities.onAuthenticated(mockk(), mockk())
verify { crashReporter wasNot Called }
every { accountManager.authenticatedAccount() } returns null
// Pretend we restart, and instantiate a new middleware instance.
val accountAbnormalities2 = AccountAbnormalities(testContext, crashReporter, this.coroutineContext)
......@@ -129,13 +129,13 @@ class AccountAbnormalitiesTest {
CompletableDeferred(Unit).also { it.complete(Unit) }
).await()
assertCaughtException(crashReporter, AbnormalFxaEvent.MissingExpectedAccountAfterStartup::class)
assertCaughtException<AbnormalFxaEvent.MissingExpectedAccountAfterStartup>(crashReporter)
}
@Test
fun `logout happy case`() = runBlocking {
val crashReporter: CrashReporter = mock()
val accountManager: FxaAccountManager = mock()
val crashReporter: CrashReporter = mockk()
val accountManager: FxaAccountManager = mockk(relaxed = true)
val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext)
accountAbnormalities.accountManagerInitializedAsync(
......@@ -144,14 +144,14 @@ class AccountAbnormalitiesTest {
).await()
// We saw an auth event, then user requested a logout.
accountAbnormalities.onAuthenticated(mock(), mock())
accountAbnormalities.onAuthenticated(mockk(), mockk())
accountAbnormalities.userRequestedLogout()
verifyZeroInteractions(crashReporter)
verify { crashReporter wasNot Called }
}
private fun <T : AbnormalFxaEvent> assertCaughtException(crashReporter: CrashReporter, type: KClass<T>) {
val captor = argumentCaptor<AbnormalFxaEvent>()
verify(crashReporter).submitCaughtException(captor.capture())
assertEquals(type, captor.value::class)
private inline fun <reified T : AbnormalFxaEvent> assertCaughtException(crashReporter: CrashReporter) {
verify {
crashReporter.submitCaughtException(any<T>())
}
}
}
......@@ -2,76 +2,74 @@ package org.mozilla.fenix.components
import android.view.View
import android.view.ViewStub
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.feature.LifecycleAwareFeature
import mozilla.components.support.test.any
import mozilla.components.support.test.mock
import org.junit.Test
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import java.lang.ref.WeakReference
class InflationAwareFeatureTest {
@Test
fun `stub inflates if no feature or view exists`() {
val stub: ViewStub = mock()
val feature: InflationAwareFeature = spy(TestableInflationAwareFeature(stub))
val stub: ViewStub = mockk(relaxed = true)
val feature: InflationAwareFeature = spyk(TestableInflationAwareFeature(stub))
feature.launch()
verify(stub).setOnInflateListener(any())
verify(stub).inflate()
verify { stub.setOnInflateListener(any()) }
verify { stub.inflate() }
}
@Test
fun `stub immediately launches if the feature is available`() {
val stub: ViewStub = mock()
val feature: InflationAwareFeature = spy(TestableInflationAwareFeature(stub))
val stub: ViewStub = mockk()
val feature: InflationAwareFeature = spyk(TestableInflationAwareFeature(stub))
feature.feature = mock()
feature.view = WeakReference(mock())
feature.feature = mockk(relaxed = true)
feature.view = WeakReference(mockk())
feature.launch()
verify(stub, never()).setOnInflateListener(any())
verify(stub, never()).inflate()
verify(feature).onLaunch(any(), any())
verify(exactly = 0) { stub.setOnInflateListener(any()) }
verify(exactly = 0) { stub.inflate() }
verify { feature.onLaunch(any(), any()) }
}
@Test
fun `feature calls stop if created`() {
val stub: ViewStub = mock()
val inflationFeature: InflationAwareFeature = spy(TestableInflationAwareFeature(stub))
val innerFeature: LifecycleAwareFeature = mock()
val stub: ViewStub = mockk()
val inflationFeature: InflationAwareFeature = spyk(TestableInflationAwareFeature(stub))
val innerFeature: LifecycleAwareFeature = mockk(relaxed = true)
inflationFeature.stop()
verify(innerFeature, never()).stop()
verify(exactly = 0) { innerFeature.stop() }
inflationFeature.feature = innerFeature
inflationFeature.stop()
verify(innerFeature).stop()
verify { innerFeature.stop() }
}
@Test
fun `start should be delegated to the inner feature`() {
val inflationFeature: InflationAwareFeature = spy(TestableInflationAwareFeature(mock()))
val innerFeature: LifecycleAwareFeature = mock()
val inflationFeature: InflationAwareFeature = spyk(TestableInflationAwareFeature(mockk()))
val innerFeature: LifecycleAwareFeature = mockk(relaxed = true)
inflationFeature.feature = innerFeature
inflationFeature.start()