Location: app/src/test/java/com/fxn/mitension/ui/viewmodel/MedicionViewModelTest.ktTests the core business logic for blood pressure measurements using coroutines and Flow testing.
@Testfun `init carga el número de medición correcto (1 si no hay datos)`() = runTest { assertEquals(1, viewModel.uiState.value.numeroMedicion)}
Test validation errors:
@Testfun `guardarMedicion emite error si los campos están vacíos`() = runTest { viewModel.guardarMedicion("Campos obligatorios", "", "") viewModel.evento.test { val evento = awaitItem() assertTrue(evento is MedicionViewModel.UiEvento.MostrarMensaje) assertEquals("Campos obligatorios", (evento as MedicionViewModel.UiEvento.MostrarMensaje).mensaje) } coVerify(exactly = 0) { repository.insertarMedicion(any()) }}
Test business rules (daily quota limit):
@Testfun `guardarMedicion emite error si el cupo del período está lleno`() = runTest { // Simulate full quota viewModel.onGuardadoExitoso() // num = 2 viewModel.onGuardadoExitoso() // num = 3 viewModel.onGuardadoExitoso() // num = 4 assertEquals(4, viewModel.uiState.value.numeroMedicion) viewModel.onSistolicaChanged("120") viewModel.onDiastolicaChanged("80") viewModel.guardarMedicion("", "Cupo lleno", "") viewModel.evento.test { val evento = awaitItem() assertTrue(evento is MedicionViewModel.UiEvento.MostrarMensaje) assertTrue((evento as MedicionViewModel.UiEvento.MostrarMensaje) .mensaje.contains("Cupo lleno")) } coVerify(exactly = 0) { repository.insertarMedicion(any()) }}
Test successful save:
@Testfun `guardarMedicion guarda con éxito si los campos son válidos`() = runTest { viewModel.onSistolicaChanged("120") viewModel.onDiastolicaChanged("80") viewModel.guardarMedicion("", "", "Éxito") viewModel.evento.test { assertTrue(awaitItem() is MedicionViewModel.UiEvento.GuardadoConExito) } coVerify(exactly = 1) { repository.insertarMedicion(any()) }}
The tests use Turbine library to test Flow emissions with test { } blocks, making it easy to verify events emitted by the ViewModel.
Location: app/src/test/java/com/fxn/mitension/util/TimeUtilsTest.ktTests time period calculation logic (morning, afternoon, night).
class TimeUtilsTest { @Test fun `obtenerPeriodoActual devuelve MAÑANA para las 10 AM`() { val calendario = Calendar.getInstance().apply { set(Calendar.HOUR_OF_DAY, 10) set(Calendar.MINUTE, 0) } val periodo = obtenerPeriodoParaCalendario(calendario) assertEquals(PeriodoDelDia.MAÑANA, periodo) } @Test fun `obtenerPeriodoActual devuelve TARDE para las 14 PM`() { val calendario = Calendar.getInstance().apply { set(Calendar.HOUR_OF_DAY, 14) set(Calendar.MINUTE, 0) } val periodo = obtenerPeriodoParaCalendario(calendario) assertEquals(PeriodoDelDia.TARDE, periodo) } @Test fun `obtenerPeriodoActual devuelve NOCHE para las 21 PM`() { val calendario = Calendar.getInstance().apply { set(Calendar.HOUR_OF_DAY, 21) set(Calendar.MINUTE, 0) } val periodo = obtenerPeriodoParaCalendario(calendario) assertEquals(PeriodoDelDia.NOCHE, periodo) }}
# Run all unit tests./gradlew test# Run unit tests for debug build./gradlew testDebugUnitTest# Run unit tests for release build./gradlew testReleaseUnitTest# Run specific test class./gradlew test --tests com.fxn.mitension.ui.viewmodel.MedicionViewModelTest# Run with verbose output./gradlew test --info
# Run unit tests in CI./gradlew test --continue# Run instrumented tests with Firebase Test Lab or emulator./gradlew connectedAndroidTest
Instrumented tests require a connected device or emulator. For CI environments, consider using Firebase Test Lab or GitHub Actions with Android emulator support.