Skip to content

Commit

Permalink
Add memory leak detection tests
Browse files Browse the repository at this point in the history
  • Loading branch information
sebaslogen committed Sep 4, 2024
1 parent bae5880 commit c70e3a1
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.sebaslogen.resacaapp.sample

import android.content.Intent
import androidx.compose.ui.test.junit4.createEmptyComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.sebaslogen.resacaapp.sample.ui.main.ComposeActivity
import com.sebaslogen.resacaapp.sample.ui.main.rememberScopedDestination
import com.sebaslogen.resacaapp.sample.utils.ComposeTestUtils
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.lang.ref.WeakReference
import kotlin.test.assertNotNull
import kotlin.test.assertNull

@RunWith(AndroidJUnit4::class)
class MemoryLeakTests : ComposeTestUtils {

private lateinit var scenario: ActivityScenario<ComposeActivity>

@get:Rule
override val composeTestRule = createEmptyComposeRule()

@Before
fun setUp() {
scenario = ActivityScenario.launch(
Intent(ApplicationProvider.getApplicationContext(), ComposeActivity::class.java).apply {
putExtra(ComposeActivity.START_DESTINATION, rememberScopedDestination)
})
}

@Test
fun givenComposeActivityWithComposablesInANestedNavigationComposable_whenTheActivityIsRecreated_thenTheOriginalComposeActivityObjectIsGarbageCollected() {
var weakActivityReference: WeakReference<ComposeActivity>? = null
// Given I create the Activity
composeTestRule.waitForIdle()
scenario.onActivity { activity: ComposeActivity ->

// And we grab a WeakReference to the Activity
weakActivityReference = WeakReference(activity)
}
printComposeUiTreeToLog()

// And I click "Navigate to rememberScoped" to get to a nested screen in the same Activity
composeTestRule.waitForIdle()
composeTestRule.onNodeWithTag("Navigate to rememberScoped").performClick()
printComposeUiTreeToLog()
composeTestRule.waitForIdle()

// When we recreate the activity
scenario.recreate()
composeTestRule.waitForIdle()

// And trigger Garbage Collection to make sure old ComposeActivity is collected
Runtime.getRuntime().gc()
printComposeUiTreeToLog()

// Then the original Activity object is garbage collected
assertNotNull(weakActivityReference) { "WeakReference container for initial ComposeActivity should not be null because it was created" }
assertNull(weakActivityReference?.get(), "Initial ComposeActivity should have been garbage collected but it wasn't, so it's leaking")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
Expand Down Expand Up @@ -122,7 +123,7 @@ fun ScreensWithNavigation(navController: NavHostController = rememberNavControll
*/
@Composable
fun NavigationButtons(navController: NavHostController) {
Button(modifier = Modifier.padding(top = 16.dp, bottom = 2.dp),
Button(modifier = Modifier.padding(top = 16.dp, bottom = 2.dp).testTag("Navigate to rememberScoped"),
onClick = { navController.navigate(rememberScopedDestination) }) {
Text(text = "Push rememberScoped destination")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.sebaslogen.resacaapp.sample

import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.performClick
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.sebaslogen.resacaapp.sample.ui.main.ComposeActivity
import com.sebaslogen.resacaapp.sample.ui.main.rememberScopedDestination
import com.sebaslogen.resacaapp.sample.utils.ComposeTestUtils
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.lang.ref.WeakReference
import kotlin.test.assertNotNull
import kotlin.test.assertNull

@RunWith(AndroidJUnit4::class)
class MemoryLeakTests : ComposeTestUtils {
init {
callFromTestInit()
}

override fun callFromTestInit() {
ComposeActivity.defaultDestination = rememberScopedDestination // This is needed to reset the destination to the default one on the release app
}

@get:Rule
override val composeTestRule = createComposeRule()

@Test
fun `given ComposeActivity with Composables in a nested Navigation Composable, when the activity is recreated, then the original ComposeActivity object is garbage collected`() {
ActivityScenario.launch(ComposeActivity::class.java).use { scenario ->
var weakActivityReference: WeakReference<ComposeActivity>? = null
// Given I create the Activity and navigate to a nested screen
scenario.onActivity { activity: ComposeActivity ->

// Given the Activity shows a screen with scoped objects
printComposeUiTreeToLog()

// And I grab a WeakReference to the Activity
weakActivityReference = WeakReference(activity)

// And I click "Navigate to rememberScoped" to get to a nested screen in the same Activity
onNodeWithTestTag("Navigate to rememberScoped").performClick()
printComposeUiTreeToLog()
}

// When we recreate the activity
scenario.recreate().onActivity {

// And trigger Garbage Collection to make sure old ComposeActivity is collected
System.gc()
printComposeUiTreeToLog()

// Then the original Activity object is garbage collected
assertNotNull(weakActivityReference) { "WeakReference container for initial ComposeActivity should not be null because it was created" }
assertNull(weakActivityReference?.get(), "Initial ComposeActivity should have been garbage collected but it wasn't, so it's leaking")
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class ScopeKeysTest : ComposeTestUtils {
init {
Expand Down

0 comments on commit c70e3a1

Please sign in to comment.