MockK: มันคืออะไร แนวคิดหลัก และไวยากรณ์

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-04-08 เวลาอ่าน: 8 นาที

MockK คือเฟรมเวิร์กแบบ Kotlin-first สำหรับสร้างออบเจกต์จำลอง (mock objects) ที่ออกแบบมาโดยเฉพาะสำหรับระบบนิเวศของ Kotlin โดยคำนึงถึงคุณลักษณะทางภาษา: โครูทีน, ฟังก์ชันส่วนขยาย, data class และ sealed class ต่างจาก Mockito ที่ถูกพอร์ตจาก Java ไปยัง Kotlin MockK ถูกออกแบบมาสำหรับไวยากรณ์ของ Kotlin ตั้งแต่แรก และไม่จำเป็นต้องมีปลั๊กอินเพิ่มเติมเพื่อทำงานกับคลาส final ตามข้อมูลจาก MockK.io ไลบรารีนี้ถูกใช้ในโปรเจกต์ Kotlin ที่มีการทดสอบหน่วยมากกว่า 40%

ประเด็นสำคัญ

  • MockK — ไลบรารีการจำลองที่เน้น Kotlin พร้อมรองรับโครูทีนและคุณสมบัติภาษา
  • mockk() — เมธอดหลักสำหรับสร้างออบเจกต์จำลอง คล้ายกับ Mockito.mock()
  • every { } — บล็อกสำหรับกำหนดค่าพฤติกรรมของ mock (stubbing) ในรูปแบบประกาศ
  • coEvery / coVerify — โครงสร้างพิเศษสำหรับทำงานกับฟังก์ชัน suspend ของโครูทีน
  • Relaxed mock — mock ที่ส่งค่าดีฟอลต์โดยไม่ต้อง stubbing อย่างชัดแจ้ง

MockK คืออะไร?

MockK คือไลบรารีสำหรับสร้างออบเจกต์จำลอง เขียนด้วย Kotlin และปรับให้เหมาะสมกับไวยากรณ์ของมัน มันแก้ปัญหาเดียวกันกับที่ Mockito แก้ — การแยกโค้ดที่ทดสอบออกจากการพึ่งพา — แต่ทำโดยใช้โครงสร้างเฉพาะของ Kotlin: แลมบ์ดา, DSL, reified generics และฟังก์ชัน suspend

ข้อได้เปรียบหลักของ MockK เหนือโซลูชันที่ถูกพอร์ตมาคือ การรองรับ Kotlin โดยกำเนิด ใน Mockito การจำลองคลาส final ต้องใช้ opt-in (mockito-inline) และเมธอดสแตติกต้องใช้ mockStatic MockK รองรับสิ่งนี้โดยดีฟอลต์ เนื่องจากคลาส Kotlin เป็น final โดยดีฟอลต์ และการเลี่ยงข้อจำกัดนี้ถูกสร้างไว้ในสถาปัตยกรรมของไลบรารี

เวอร์ชัน 1.13.12 (2024) เป็นรุ่นเสถียรที่รองรับ Kotlin 2.0, คอมไพเลอร์ K2 และโปรเจกต์ข้ามแพลตฟอร์ม (KMP) MockK ยังทำงานกับ Kotlin/Native และ Kotlin/JS ทำให้เป็นตัวเลือกเดียวสำหรับโปรเจกต์ KMP ที่ทั้ง Mockito และ EasyMock ไม่สามารถใช้งานได้

MockK ถูกออกแบบโดยคำนึงถึงลักษณะเฉพาะของ Kotlin และใช้คุณสมบัติภาษา — reified generics, DSL กับแลมบ์ดา, ฟังก์ชัน inline — เพื่อให้ API ที่กระชับและ type-safe โดยไม่เสียสละประสิทธิภาพ

MockK ทำงานอย่างไร

กลไกของ MockK อาศัยการตรวจสอบไบต์โค้ดผ่านไลบรารี ByteBuddy (เช่นเดียวกับ Mockito) แต่ห่อหุ้มไว้ใน DSL ที่เป็นมิตรกับ Kotlin แทนที่จะใช้ลูกโซ่ when().thenReturn() MockK ใช้บล็อกแลมบ์ดา every { } และ coEvery { } ที่ดูเหมือนส่วนขยายตามธรรมชาติของภาษา ภายใต้ฝาครอบ MockK จะสกัดกั้นการเรียกภายในแลมบ์ดา วิเคราะห์เมธอดและอาร์กิวเมนต์ผ่านรีเฟลกชัน และจับคู่กับกฎ stubbing ที่บันทึกไว้

ไวยากรณ์พื้นฐานของ MockK

บล็อก every { mock.method() } returns value อ่านว่า “ทุกครั้งที่เรียกเมธอด ให้ส่งค่ากลับ” ไวยากรณ์แบบประกาศนี้ใกล้เคียงกับสไตล์ Kotlin มากกว่าและขจัดความสับสนเกี่ยวกับลำดับอาร์กิวเมนต์ใน when() ต้องขอบคุณ reified generics ของ Kotlin ทำให้ชนิดของ mock ถูกอนุมานโดยอัตโนมัติโดยไม่ต้องระบุคลาสอย่างชัดเจน

kotlin
val repository = mockk<UserRepository>()

// Stubbing: การเรียก findById(1) แต่ละครั้งส่งผู้ใช้กลับ
every { repository.findById(1) } returns User("Alice")

// การเรียกและการตรวจสอบ
val result = repository.findById(1)
assertEquals("Alice", result.name)

Relaxed mock: โบยเลอร์เพลทน้อยลง

ต่างจาก Mockito ที่แต่ละเมธอดต้องกำหนดค่าอย่างชัดเจน MockK รองรับ relaxed mock — mock ที่ส่งค่าดีฟอลต์ที่ “สมเหตุสมผล” สำหรับเมธอดใดๆ: รายการว่างสำหรับ List, 0 สำหรับ Int, สตริงว่างสำหรับ String ซึ่งช่วยลดปริมาณโค้ดตั้งค่าได้อย่างมาก

kotlin
// Relaxed mock — เมธอดทั้งหมดส่งค่าดีฟอลต์
val api = mockk<ApiService>(relaxed = true)

// ไม่ต้อง stubbing — ส่งรายการว่างกลับ
println(api.getUsers()) // []

การสร้าง mock และ mock แบบผ่อนคลาย

MockK มีหลายวิธีในการสร้างออบเจกต์จำลอง: mockk<T>() สำหรับ mock แบบเข้มงวด (แต่ละเมธอดต้องกำหนดค่าอย่างชัดเจน), mockk<T>(relaxed = true) สำหรับ mock แบบผ่อนคลาย และ spyk(obj) สำหรับสร้าง spy บนออบเจกต์จริง

ฟังก์ชันชนิดพฤติกรรมโดยไม่มี stubbing
mockk()Mock แบบเข้มงวดโยนข้อยกเว้นเมื่อเรียกเมธอดที่ไม่ได้ stub
mockk(relaxed = true)Mock แบบผ่อนคลายส่งค่าดีฟอลต์
spyk()Spyเรียกเมธอดจริงถ้าไม่ได้กำหนด stub
slot()Argument Captorจับอาร์กิวเมนต์สำหรับการตรวจสอบ

การเลือกระหว่าง mock แบบเข้มงวดและผ่อนคลายขึ้นอยู่กับบริบท Mock แบบเข้มงวดรับประกันว่าการทดสอบจะไม่ใช้เมธอดที่ไม่ได้กำหนดพฤติกรรม — ซึ่งเพิ่มความน่าเชื่อถือ Mock แบบผ่อนคลายสะดวกสำหรับการสร้างต้นแบบการทดสอบอย่างรวดเร็วเมื่อการพึ่งพาทั้งหมดไม่สำคัญ ในทางปฏิบัติ แนะนำให้เริ่มต้นด้วย mock แบบเข้มงวดและเปลี่ยนเป็นแบบผ่อนคลายเฉพาะเมื่อ stubbing ใช้บรรทัดมากกว่าการทดสอบเอง

Stubbing: การกำหนดค่าพฤติกรรมด้วยบล็อก every

บล็อก every คือโครงสร้างหลักของ stubbing ใน MockK ภายในแลมบ์ดา มีการอธิบายการเรียกเมธอดด้วยอาร์กิวเมนต์เฉพาะ จากนั้นส่งค่ากลับผ่าน returns, โยนข้อยกเว้นผ่าน throws หรือคำนวณคำตอบผ่าน answers

วิธีการ stubbing แบบต่างๆ

MockK รองรับสถานการณ์ทั้งหมดที่จำเป็นสำหรับการทดสอบ: การส่งค่ากลับ, การโยนข้อยกเว้น, การคำนวณคำตอบตามอาร์กิวเมนต์, และคำตอบหลายรายการตามลำดับ (ลำดับการเรียก)

kotlin
// ส่งค่ากลับ
every { repo.findById(1) } returns User("Alice")

// โยนข้อยกเว้น
every { repo.findById(999) } throws NotFoundException()

// คำตอบแบบไดนามิก
every { repo.save(any()) } answers {
    val user = firstArg<User>()
    user.copy(id = 42)
}

// ลำดับคำตอบ
every { repo.findAll() } returnsMany listOf(
    listOf(User("Alice")),
    listOf(User("Bob")),
    emptyList()
)

Verify และ coVerify สำหรับโครูทีน

Verify ใน MockK มีวัตถุประสงค์คล้ายกับ Mockito.verify() แต่ใช้ DSL ของ Kotlin: verify { mock.method() } สำหรับฟังก์ชัน suspend ใช้ coVerify { mock.suspendMethod() } ซึ่งทำงานถูกต้องกับโครูทีนและไม่ต้องการรันเนอร์พิเศษ

การตรวจสอบจำนวนครั้งที่เรียก

MockK รองรับตัวปรับแต่งเดียวกันกับ Mockito: exactly(1), atLeast(2), atMost(5), wasNot(Called) ไวยากรณ์เรียบง่าย — ตัวปรับแต่งถูกส่งเป็นอาร์กิวเมนต์แรกใน verify { }

kotlin
// การตรวจสอบ: เมธอดถูกเรียก 1 ครั้งพอดี
verify(exactly = 1) { repo.save(any()) }

// ตรวจสอบลำดับการเรียก
verifySequence {
    repo.save(any())
    repo.flush()
}

// coVerify สำหรับฟังก์ชัน suspend
coVerify { api.fetchUsers() }

Slot: การจับอาร์กิวเมนต์

สำหรับการตรวจสอบอาร์กิวเมนต์ ใช้ slot() — ซึ่งคล้ายกับ ArgumentCaptor Slot ถูกประกาศก่อนการเรียก ส่งต่อไปยัง every หรือ verify และหลังจากการทดสอบทำงาน จะมีค่าที่ถูกจับไว้

kotlin
val userSlot = slot<User>()

verify { repo.save(capture(userSlot)) }

assertEquals("Alice", userSlot.captured.name)

คำอธิบายประกอบ MockK และการรวม JUnit

MockK มีคำอธิบายประกอบ @MockK และ @RelaxedMockK สำหรับสร้าง mock ผ่านการเริ่มต้นใน JUnit 5 ส่วนขยาย MockKExtension จะสร้าง mock โดยอัตโนมัติก่อนการทดสอบแต่ละครั้งและทำความสะอา�ะหลัง — คล้ายกับ MockitoExtension แต่รองรับโหมดผ่อนคลาย

ตัวอย่างกับ MockKExtension

คำอธิบายประกอบ @InjectMockKs (หรือทางเลือก @MockK พร้อมการสร้างออบเจกต์อย่างชัดแจ้ง) ฉีด mock เข้าไปในอินสแตนซ์ที่ทดสอบ ซึ่งช่วยลดโบยเลอร์เพลทและทำให้โค้ดทดสอบสะอาดขึ้น

kotlin
@ExtendWith(MockKExtension::class)
class UserServiceTest {

    @MockK
    lateinit var repository: UserRepository

    @InjectMockKs
    lateinit var service: UserService

    @Test
    fun `getUser returns user from repository`() {
        every { repository.findById(1) } returns User("Alice")
        assertEquals("Alice", service.getUser(1)?.name)
    }
}

MockK vs Mockito: เลือกอะไรสำหรับ Kotlin

การเลือกระหว่าง MockK และ Mockito ขึ้นอยู่กับองค์ประกอบของทีมและชนิดของโปรเจกต์ Mockito มีระบบนิเวศที่ใหญ่กว่า ตัวอย่างและการรวมระบบมากกว่า แต่ MockK ให้ไวยากรณ์ Kotlin ที่สะอาดกว่าและการรองรับคุณสมบัติภาษาโดยกำเนิด สำหรับโปรเจกต์ Kotlin ใหม่ MockK ถูกแนะนำให้เป็นโซลูชันที่เป็นธรรมชาติมากกว่า

เกณฑ์MockKMockito
ไวยากรณ์Kotlin DSL (every, verify)สไตล์ Java (when, thenReturn)
โครูทีนcoEvery, coVerify (โดยกำเนิด)ต้องใช้ไลบรารีเพิ่มเติม
คลาส finalรองรับโดยดีฟอลต์ต้องใช้ mockito-inline
KMPรองรับไม่รองรับ
Relaxed mockในตัวไม่มีสิ่งที่เทียบเท่า
ความนิยมเพิ่มขึ้นในชุมชน Kotlinโดดเด่นในโปรเจกต์ Java และไฮบริด

สำหรับโปรเจกต์ Kotlin บริสุทธิ์ (ไม่มีคลาส Java) MockK เหมาะกว่า: โบยเลอร์เพลทน้อยกว่า, รองรับโครูทีนโดยกำเนิด, ไม่มีเซอร์ไพรส์กับคลาส final สำหรับโปรเจกต์ไฮบริดหรือทีมที่มีพื้นฐาน Java MockKit ยังคงเป็นตัวเลือกที่ใช้งานได้ — ไลบรารีทั้งสองสามารถใช้ในโปรเจกต์เดียวกันผ่านโมดูลต่างๆ เมื่อย้ายจาก Mockito ไปยัง MockK ก็เพียงพอที่จะแทนที่คำอธิบายประกอบ @Mock ด้วย @MockK และเขียนบล็อก when().thenReturn() ใหม่ในรูปแบบ every { }

คำถามที่พบบ่อย

Relaxed mock แตกต่างจาก mock ปกติใน MockK อย่างไร?

Relaxed mock ส่งค่าดีฟอลต์สำหรับเมธอดที่ไม่ได้ stub ทั้งหมด (รายการว่าง, 0, null) โดยไม่โยนข้อยกเว้น Mock ปกติ (แบบเข้มงวด) ต้องการ stubbing อย่างชัดแจ้งของแต่ละเมธอด — มิฉะนั้นการทดสอบจะล้มเหลว Relaxed mock สะดวกสำหรับการทดสอบรวดเร็ว แบบเข้มงวดสำหรับการทดสอบที่เชื่อถือได้

จะจำลองฟังก์ชันส่วนขยายใน MockK ได้อย่างไร?

MockK รองรับการจำลองฟังก์ชันส่วนขยายผ่าน mockkStatic() สิ่งนี้เป็นไปได้เพราะฟังก์ชันส่วนขยายใน Kotlin เป็นเมธอดสแตติกที่มีตัวรับเป็นพารามิเตอร์แรก สำหรับฟังก์ชันส่วนขยายแต่ละตัว คุณต้องระบุคลาสที่ประกาศไว้

MockK ทำงานกับ Kotlin Multiplatform หรือไม่?

ใช่ MockK รองรับ Kotlin Multiplatform (KMP) สำหรับโค้ดทั่วไป บนแพลตฟอร์ม JVM, Native และ JS คุณสามารถใช้ API ทั่วไป: mockk(), every, verify สิ่งนี้ทำให้ MockK เป็นตัวเลือกเดียวสำหรับโปรเจกต์ KMP ที่ Mockito ไม่ทำงาน

จะตรวจสอบลำดับการเรียกใน MockK ได้อย่างไร?

ใช้ verifySequence { } — บล็อกที่ระบุการเรียกตามลำดับที่คาดหวังอย่างเคร่งครัด หากลำดับจริงต่างออกไป verifySequence จะโยนข้อยกเว้นระบุการเรียกที่ไม่ตรงกันครั้งแรก

สามารถใช้ MockK และ Mockito ในโปรเจกต์เดียวกันได้หรือไม่?

ใช่ ในทางเทคนิค เป็นไปได้ แต่ไม่แนะนำ ความขัดแย้งอาจเกิดขึ้นที่ระดับการตรวจสอบไบต์โค้ด (ByteBuddy vs mockito-inline) หากโปรเจกต์ใช้ MockKit อยู่แล้ว การย้ายไปยัง MockK สามารถทำได้ทีละน้อยผ่านการแยกโมดูล

สรุป

  • MockK — ไลบรารีการจำลองแบบ Kotlin-first พร้อมรองรับภาษาโดยกำเนิด
  • every { } — DSL แบบประกาศสำหรับกำหนดค่าพฤติกรรมของ mock
  • coEvery / coVerify — รองรับฟังก์ชัน suspend ในโครูทีนโดยไม่ต้องพึ่งพาเพิ่มเติม
  • Relaxed mock — mock ที่มีค่าดีฟอลต์ซึ่งช่วยลดโบยเลอร์เพลท
  • @MockK / @InjectMockKs — คำอธิบายประกอบสำหรับสร้าง mock อัตโนมัติใน JUnit 5
  • MockK vs Mockito — MockK เหมาะกว่าสำหรับโปรเจกต์ Kotlin บริสุทธิ์และ KMP
  • verifySequence — การตรวจสอบลำดับการเรียกเมธอดอย่างเคร่งครัด

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม