mifos-mobile/claude-product-cycle/testing-layer/templates/ViewModelTest.kt.template
Rajan Maurya b351148ac2
feat(testing): add core:testing module with fakes, fixtures, and TestTags (#3059)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 01:43:35 -05:00

200 lines
7.4 KiB
Plaintext

package org.mifos.mobile.feature.${feature_lowercase}
import app.cash.turbine.test
import kotlin.test.BeforeTest
import kotlin.test.AfterTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.assertNull
import kotlin.test.assertNotNull
import kotlinx.coroutines.test.runTest
import org.mifos.mobile.core.testing.fake.Fake${Feature}Repository
import org.mifos.mobile.core.testing.fixtures.${Feature}Fixtures
import org.mifos.mobile.core.testing.rule.MainDispatcherRule
class ${Feature}ViewModelTest {
// ═══════════════════════════════════════════════════════════════
// SETUP
// ═══════════════════════════════════════════════════════════════
private val mainDispatcherRule = MainDispatcherRule()
private lateinit var viewModel: ${Feature}ViewModel
private lateinit var fakeRepository: Fake${Feature}Repository
@BeforeTest
fun setup() {
fakeRepository = Fake${Feature}Repository()
viewModel = ${Feature}ViewModel(
repository = fakeRepository
)
}
@AfterTest
fun teardown() {
fakeRepository.reset()
}
// ═══════════════════════════════════════════════════════════════
// INITIAL STATE TESTS
// ═══════════════════════════════════════════════════════════════
@Test
fun `initial state is loading`() = runTest {
viewModel.stateFlow.test {
val state = awaitItem()
assertTrue(state.uiState is ${Feature}UiState.Loading)
}
}
@Test
fun `initial dialog state is null`() = runTest {
viewModel.stateFlow.test {
val state = awaitItem()
assertNull(state.dialogState)
}
}
// ═══════════════════════════════════════════════════════════════
// SUCCESS STATE TESTS
// ═══════════════════════════════════════════════════════════════
@Test
fun `load success updates state with data`() = runTest {
// Given
val testData = ${Feature}Fixtures.createList(5)
fakeRepository.setLoadSuccess(testData)
// When
viewModel.loadData()
// Then
viewModel.stateFlow.test {
val state = expectMostRecentItem()
assertTrue(state.uiState is ${Feature}UiState.Success)
assertEquals(testData, (state.uiState as ${Feature}UiState.Success).data)
}
}
@Test
fun `empty data shows success with empty list`() = runTest {
// Given
fakeRepository.setLoadEmpty()
// When
viewModel.loadData()
// Then
viewModel.stateFlow.test {
val state = expectMostRecentItem()
assertTrue(state.uiState is ${Feature}UiState.Success)
assertTrue((state.uiState as ${Feature}UiState.Success).data.isEmpty())
}
}
// ═══════════════════════════════════════════════════════════════
// ERROR STATE TESTS
// ═══════════════════════════════════════════════════════════════
@Test
fun `load error shows error state`() = runTest {
// Given
fakeRepository.setLoadError("Network error")
// When
viewModel.loadData()
// Then
viewModel.stateFlow.test {
val state = expectMostRecentItem()
assertTrue(state.uiState is ${Feature}UiState.Error)
assertEquals("Network error", (state.uiState as ${Feature}UiState.Error).message)
}
}
// ═══════════════════════════════════════════════════════════════
// ACTION TESTS
// ═══════════════════════════════════════════════════════════════
@Test
fun `refresh action reloads data`() = runTest {
// Given
fakeRepository.setLoadSuccess(emptyList())
// When
viewModel.loadData()
viewModel.trySendAction(${Feature}Action.Refresh)
// Then
assertEquals(2, fakeRepository.loadCallCount)
}
@Test
fun `item click triggers navigation event`() = runTest {
// Given
val itemId = 1L
// When
viewModel.trySendAction(${Feature}Action.ItemClicked(itemId))
// Then
viewModel.eventFlow.test {
val event = awaitItem()
assertEquals(${Feature}Event.NavigateToDetail(itemId), event)
}
}
// ═══════════════════════════════════════════════════════════════
// DIALOG TESTS
// ═══════════════════════════════════════════════════════════════
@Test
fun `delete action shows confirmation dialog`() = runTest {
// Given
val itemId = 1L
// When
viewModel.trySendAction(${Feature}Action.DeleteClicked(itemId))
// Then
viewModel.stateFlow.test {
val state = expectMostRecentItem()
assertNotNull(state.dialogState)
assertTrue(state.dialogState is DialogState.Confirmation)
}
}
@Test
fun `dismiss dialog clears dialog state`() = runTest {
// Given
viewModel.trySendAction(${Feature}Action.DeleteClicked(1L))
// When
viewModel.trySendAction(${Feature}Action.DismissDialog)
// Then
viewModel.stateFlow.test {
val state = expectMostRecentItem()
assertNull(state.dialogState)
}
}
@Test
fun `confirm delete calls repository`() = runTest {
// Given
fakeRepository.setDeleteSuccess()
// When
viewModel.trySendAction(${Feature}Action.ConfirmDelete(itemId = 1L))
// Then
assertEquals(1, fakeRepository.deleteCallCount)
}
// ═══════════════════════════════════════════════════════════════
// TODO: Add more tests for specific feature logic
// ═══════════════════════════════════════════════════════════════
}