package com.example.myapp.data.repository
import com.example.myapp.data.local.PostDao
import com.example.myapp.data.local.PostEntity
import com.example.myapp.data.remote.ApiService
import com.example.myapp.models.Post
import io.mockk.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@OptIn(ExperimentalCoroutinesApi::class)
class PostRepositoryTest {
private lateinit var repository: PostRepositoryImpl
private lateinit var apiService: ApiService
private lateinit var postDao: PostDao
private lateinit var mapper: PostMapper
@Before
fun setup() {
apiService = mockk()
postDao = mockk(relaxed = true)
mapper = PostMapper()
repository = PostRepositoryImpl(apiService, postDao, mapper)
}
@After
fun tearDown() {
unmockkAll()
}
@Test
fun `fetchPosts should return success when API call succeeds`() = runTest {
// Given
val mockPosts = listOf(
Post(id = 1, title = "Test Post", body = "Content",
authorId = 1, createdAt = 0L)
)
val response = PostsResponse(posts = mockPosts, meta = Meta(1, 20, 1))
coEvery { apiService.getPosts(any(), any()) } returns response
// When
val result = repository.fetchPosts()
// Then
assertTrue(result.isSuccess)
assertEquals(mockPosts, result.getOrNull())
coVerify { postDao.insertAll(any()) }
}
@Test
fun `fetchPosts should return cached data when API call fails`() = runTest {
// Given
val cachedEntities = listOf(
PostEntity(id = 1, title = "Cached", body = "Content",
authorId = 1, createdAt = 0L)
)
coEvery { apiService.getPosts(any(), any()) } throws Exception("Network error")
coEvery { postDao.getAllPosts() } returns cachedEntities
// When
val result = repository.fetchPosts()
// Then
assertTrue(result.isSuccess)
val posts = result.getOrNull()
assertEquals(1, posts?.size)
assertEquals("Cached", posts?.first()?.title)
}
@Test
fun `observePosts should emit sorted posts from DAO`() = runTest {
// Given
val entities = listOf(
PostEntity(id = 1, title = "Old", body = "Content",
authorId = 1, createdAt = 1000L),
PostEntity(id = 2, title = "New", body = "Content",
authorId = 1, createdAt = 2000L)
)
every { postDao.observePosts() } returns flowOf(entities)
// When
val posts = mutableListOf<List<Post>>()
repository.observePosts().collect { posts.add(it) }
// Then
assertEquals(1, posts.size)
assertEquals(2, posts.first().size)
assertEquals("New", posts.first().first().title)
}
@Test
fun `createPost should call API and cache result`() = runTest {
// Given
val post = Post(id = 0, title = "New Post", body = "Content",
authorId = 1, createdAt = 0L)
val createdPost = post.copy(id = 123)
val response = PostResponse(post = createdPost)
val requestSlot = slot<CreatePostRequest>()
coEvery {
apiService.createPost(capture(requestSlot))
} returns response
// When
val result = repository.createPost(post)
// Then
assertTrue(result.isSuccess)
assertEquals("New Post", requestSlot.captured.title)
coVerify { postDao.insert(any()) }
}
}
package com.example.myapp.ui
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.example.myapp.data.repository.PostRepository
import com.example.myapp.models.Post
import io.mockk.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.*
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@OptIn(ExperimentalCoroutinesApi::class)
class PostViewModelTest {
@get:Rule
val instantExecutorRule = InstantTaskExecutorRule()
private val testDispatcher = StandardTestDispatcher()
private lateinit var viewModel: PostViewModel
private lateinit var repository: PostRepository
@Before
fun setup() {
Dispatchers.setMain(testDispatcher)
repository = mockk()
viewModel = PostViewModel(repository)
}
@After
fun tearDown() {
Dispatchers.resetMain()
unmockkAll()
}
@Test
fun `initial state should be loading`() = runTest {
val state = viewModel.uiState.value
assertTrue(state.posts.isEmpty())
}
@Test
fun `loadPosts should update state with posts on success`() = runTest {
// Given
val mockPosts = listOf(
Post(id = 1, title = "Test", body = "Content",
authorId = 1, createdAt = 0L)
)
coEvery { repository.fetchPosts() } returns Result.success(mockPosts)
// When
viewModel.loadPosts()
testDispatcher.scheduler.advanceUntilIdle()
// Then
val state = viewModel.uiState.value
assertEquals(mockPosts, state.posts)
assertFalse(state.isLoading)
assertEquals(null, state.error)
}
@Test
fun `loadPosts should update state with error on failure`() = runTest {
// Given
val errorMessage = "Network error"
coEvery {
repository.fetchPosts()
} returns Result.failure(Exception(errorMessage))
// When
viewModel.loadPosts()
testDispatcher.scheduler.advanceUntilIdle()
// Then
val state = viewModel.uiState.value
assertFalse(state.isLoading)
assertEquals(errorMessage, state.error)
}
@Test
fun `observePosts should collect posts from repository`() = runTest {
// Given
val mockPosts = listOf(
Post(id = 1, title = "Test", body = "Content",
authorId = 1, createdAt = 0L)
)
every { repository.observePosts() } returns flowOf(mockPosts)
// Re-create ViewModel to trigger init block
viewModel = PostViewModel(repository)
testDispatcher.scheduler.advanceUntilIdle()
// Then
val state = viewModel.uiState.value
assertEquals(mockPosts, state.posts)
}
}