From 59f62a22b2a52237d2578637aaaa9c7a19c558d9 Mon Sep 17 00:00:00 2001 From: nk Date: Mon, 1 Jun 2026 13:23:11 +0300 Subject: [PATCH] test: add unit tests for use cases and ViewModel, add Compose UI test --- .../radiola/ui/stations/StationsScreenTest.kt | 25 +++++++ .../SearchTrackInServiceUseCaseTest.kt | 29 ++++++++ .../usecase/ToggleFavoriteUseCaseTest.kt | 37 ++++++++++ .../ui/stations/StationsViewModelTest.kt | 69 +++++++++++++++++++ 4 files changed, 160 insertions(+) create mode 100644 app/src/androidTest/java/com/radiola/ui/stations/StationsScreenTest.kt create mode 100644 app/src/test/java/com/radiola/domain/usecase/SearchTrackInServiceUseCaseTest.kt create mode 100644 app/src/test/java/com/radiola/domain/usecase/ToggleFavoriteUseCaseTest.kt create mode 100644 app/src/test/java/com/radiola/ui/stations/StationsViewModelTest.kt diff --git a/app/src/androidTest/java/com/radiola/ui/stations/StationsScreenTest.kt b/app/src/androidTest/java/com/radiola/ui/stations/StationsScreenTest.kt new file mode 100644 index 0000000..88af711 --- /dev/null +++ b/app/src/androidTest/java/com/radiola/ui/stations/StationsScreenTest.kt @@ -0,0 +1,25 @@ +package com.radiola.ui.stations + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import com.radiola.ui.theme.RadiolaTheme +import org.junit.Rule +import org.junit.Test + +class StationsScreenTest { + + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun stationsScreen_showsTitle() { + composeTestRule.setContent { + RadiolaTheme { + StationsScreen(onStationClick = {}) + } + } + + composeTestRule.onNodeWithText("Радио").assertIsDisplayed() + } +} diff --git a/app/src/test/java/com/radiola/domain/usecase/SearchTrackInServiceUseCaseTest.kt b/app/src/test/java/com/radiola/domain/usecase/SearchTrackInServiceUseCaseTest.kt new file mode 100644 index 0000000..ce2c107 --- /dev/null +++ b/app/src/test/java/com/radiola/domain/usecase/SearchTrackInServiceUseCaseTest.kt @@ -0,0 +1,29 @@ +package com.radiola.domain.usecase + +import com.radiola.domain.model.DeeplinkService +import com.radiola.domain.model.Track +import org.junit.Assert.assertTrue +import org.junit.Test + +class SearchTrackInServiceUseCaseTest { + + private val useCase = SearchTrackInServiceUseCase() + + @Test + fun `invoke returns correct Yandex Music URL`() { + val track = Track("Artist", "Song", null, "Record") + val url = useCase(track, DeeplinkService.YANDEX) + + assertTrue(url.startsWith("https://music.yandex.ru/search")) + assertTrue(url.contains("Artist")) + assertTrue(url.contains("Song")) + } + + @Test + fun `invoke returns correct Spotify URL`() { + val track = Track("Artist", "Song", null, "Record") + val url = useCase(track, DeeplinkService.SPOTIFY) + + assertTrue(url.startsWith("https://open.spotify.com/search")) + } +} diff --git a/app/src/test/java/com/radiola/domain/usecase/ToggleFavoriteUseCaseTest.kt b/app/src/test/java/com/radiola/domain/usecase/ToggleFavoriteUseCaseTest.kt new file mode 100644 index 0000000..b3b70d0 --- /dev/null +++ b/app/src/test/java/com/radiola/domain/usecase/ToggleFavoriteUseCaseTest.kt @@ -0,0 +1,37 @@ +package com.radiola.domain.usecase + +import com.radiola.domain.model.Station +import com.radiola.domain.repository.FavoritesRepository +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Test + +class ToggleFavoriteUseCaseTest { + + private val favoritesRepository: FavoritesRepository = mockk(relaxed = true) + private val useCase = ToggleFavoriteUseCase(favoritesRepository) + + @Test + fun `invoke adds favorite when not favorite`() = runTest { + val station = Station(1, "Record", "rr", "", "", "", emptyList(), 0) + coEvery { favoritesRepository.isFavorite(station.id) } returns flowOf(false) + + useCase(station) + + coVerify { favoritesRepository.addFavorite(station) } + } + + @Test + fun `invoke removes favorite when already favorite`() = runTest { + val station = Station(1, "Record", "rr", "", "", "", emptyList(), 0) + coEvery { favoritesRepository.isFavorite(station.id) } returns flowOf(true) + + useCase(station) + + coVerify { favoritesRepository.removeFavorite(station.id) } + } +} diff --git a/app/src/test/java/com/radiola/ui/stations/StationsViewModelTest.kt b/app/src/test/java/com/radiola/ui/stations/StationsViewModelTest.kt new file mode 100644 index 0000000..281bac2 --- /dev/null +++ b/app/src/test/java/com/radiola/ui/stations/StationsViewModelTest.kt @@ -0,0 +1,69 @@ +package com.radiola.ui.stations + +import com.radiola.domain.model.Station +import com.radiola.domain.usecase.GetStationsUseCase +import com.radiola.domain.usecase.PlayStationUseCase +import com.radiola.domain.usecase.ToggleFavoriteUseCase +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class StationsViewModelTest { + + private val testDispatcher = StandardTestDispatcher() + private val getStationsUseCase: GetStationsUseCase = mockk() + private val playStationUseCase: PlayStationUseCase = mockk(relaxed = true) + private val toggleFavoriteUseCase: ToggleFavoriteUseCase = mockk(relaxed = true) + + @Before + fun setup() { + Dispatchers.setMain(testDispatcher) + every { getStationsUseCase() } returns flowOf( + listOf( + Station(1, "Record", "rr", "", "", "Trans", listOf("trans"), 0), + Station(2, "Deep", "deep", "", "", "House", listOf("house"), 1) + ) + ) + } + + @After + fun tearDown() { + Dispatchers.resetMain() + } + + @Test + fun `stations filtered by search query`() = runTest { + val viewModel = StationsViewModel(getStationsUseCase, playStationUseCase, toggleFavoriteUseCase) + advanceUntilIdle() + + viewModel.onSearchQueryChange("Record") + advanceUntilIdle() + + assertEquals(1, viewModel.stations.value.size) + assertEquals("Record", viewModel.stations.value[0].name) + } + + @Test + fun `playStation calls use case`() = runTest { + val viewModel = StationsViewModel(getStationsUseCase, playStationUseCase, toggleFavoriteUseCase) + val station = Station(1, "Record", "rr", "", "", "", emptyList(), 0) + + viewModel.playStation(station) + advanceUntilIdle() + + coVerify { playStationUseCase(station) } + } +}