Because the Domain layer has zero Android dependencies, it’s trivial to unit test with plain JUnit and MockK for mocking:

class GetPopularMoviesUseCaseTest {
    private val repository: MovieRepository = mockk()
    private val useCase = GetPopularMoviesUseCase(repository)

    @Test
    fun `returns success when repository succeeds`() = runTest {
        val fakeMovies = listOf(Movie(1, "Inception", 2010))
        coEvery { repository.getPopularMovies() } returns NetworkResult.Success(fakeMovies)

        val result = useCase()

        assertEquals(NetworkResult.Success(fakeMovies), result)
        coVerify(exactly = 1) { repository.getPopularMovies() }
    }
}

runTest (from kotlinx-coroutines-test) runs suspend test bodies on a controlled test dispatcher, so tests involving coroutines run deterministically and instantly rather than needing real delays.

UI testing a Composable

@get:Rule
val composeTestRule = createComposeRule()

@Test
fun movieCard_displaysTitle() {
    composeTestRule.setContent {
        MovieCard(movie = Movie(1, "Inception", 2010), onClick = {})
    }
    composeTestRule.onNodeWithText("Inception").assertIsDisplayed()
}

A healthy test pyramid for a production app has many fast unit tests on the Domain/Data layers, fewer Compose UI tests on individual screens, and only a handful of full end-to-end tests.