Unit testing with JUnit and MockK
Alex Chen
Jan 2026
2 tabs
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)
}
}
2 files · kotlin
Explain with highlit
Unit testing verifies individual components in isolation. I use JUnit 5 or JUnit 4 as the test framework with @Test annotations. MockK provides Kotlin-friendly mocking—mockk<T>() creates mocks, every { } stubs behavior, verify { } confirms calls. Coroutines testing uses runTest or runBlockingTest with TestDispatchers. ViewModels test using InstantTaskExecutorRule for LiveData. Repository tests mock DAOs and API services. coEvery handles suspend functions. slot() captures arguments. Assertions use assertEquals, assertTrue, or libraries like Truth or AssertJ. Testing ensures code correctness and enables refactoring confidence.