PPrepLearn
Progress Β· 0/10 sections

πŸ§ͺ πŸ§ͺ Testing

2 min read Β· Notion


Unit testing a ViewModel
@ExperimentalCoroutinesApi
class UserViewModelTest {
    @get:Rule
    val coroutineRule = MainDispatcherRule()
 
    private val mockRepo = mockk<UserRepository>()
    private lateinit var viewModel: UserViewModel
 
    @Before
    fun setup() {
        viewModel = UserViewModel(mockRepo)
    }
 
    @Test
    fun `loadUser emits success state`() = runTest {
        coEvery { mockRepo.getUser(1) } returns User(1, "Ashu")
        viewModel.loadUser(1)
        assertEquals(UserState.Success(User(1, "Ashu")), viewModel.uiState.value)
    }
}
MainDispatcherRule for coroutines
class MainDispatcherRule(
    private val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
) : TestWatcher() {
    override fun starting(desc: Description) {
        Dispatchers.setMain(dispatcher)
    }
    override fun finished(desc: Description) {
        Dispatchers.resetMain()
        dispatcher.cleanupTestCoroutines()
    }
}

Replace Dispatchers.Main in tests to run coroutines synchronously.

How to test StateFlow / LiveData
// StateFlow
@Test
fun `state updates correctly`() = runTest {
    val values = mutableListOf<UiState>()
    val job = launch { viewModel.uiState.toList(values) }
    viewModel.loadData()
    job.cancel()
    assertEquals(UiState.Loading, values[0])
    assertTrue(values[1] is UiState.Success)
}
 
// LiveData (use InstantTaskExecutorRule)
@get:Rule
val liveDataRule = InstantTaskExecutorRule()
Mocking with MockK
val repo = mockk<UserRepository>()
coEvery { repo.getUser(any()) } returns User(1, "Ashu")
coVerify { repo.getUser(1) }
 
// Relaxed mock (doesn't fail on unstubbed calls)
val relaxedRepo = mockk<UserRepository>(relaxed = true)

MockK is the preferred Kotlin mocking library (over Mockito) because it's coroutine-aware.

Testing Room Database
@RunWith(AndroidJUnit4::class)
class UserDaoTest {
    private lateinit var db: AppDatabase
    private lateinit var dao: UserDao
 
    @Before
    fun setup() {
        db = Room.inMemoryDatabaseBuilder(
            ApplicationProvider.getApplicationContext(),
            AppDatabase::class.java
        ).allowMainThreadQueries().build()
        dao = db.userDao()
    }
 
    @After
    fun tearDown() { db.close() }
 
    @Test
    fun insertAndRetrieve() = runTest {
        dao.insert(User(1, "Ashu"))
        val users = dao.getAll().first()
        assertEquals(1, users.size)
    }
}
Compose UI Testing
@get:Rule
val composeRule = createComposeRule()
 
@Test
fun myComposableTest() {
    composeRule.setContent {
        MyComposable(text = "Hello")
    }
    composeRule.onNodeWithText("Hello").assertIsDisplayed()
    composeRule.onNodeWithTag("submit_button").performClick()
    composeRule.onNodeWithText("Success").assertExists()
}

Related