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()) }
    }
}
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.