Mock — คืออะไร, ออบเจกต์ม็อก และไลบรารีสำหรับการทดสอบ

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

Mock คือออบเจกต์ทดแทนที่เลียนแบบพฤติกรรมของคอมโพเนนต์จริง และช่วยให้ตรวจสอบการโต้ตอบกับมันได้ ต่างจาก Stub ที่เพียงคืนค่าที่กำหนดไว้ล่วงหน้า Mock จะบันทึกข้อเท็จจริงของการเรียกเมธอด อาร์กิวเมนต์ที่ส่งผ่าน และจำนวนการเรียก ตามข้อมูลจาก Mockito (2024) Mock เป็นประเภท Test Double ที่ได้รับความนิยมมากที่สุดในโปรเจกต์ Java และ Kotlin ใช้ในการทดสอบหน่วยมากกว่า 70% ของแอปพลิเคชันมือถือ

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

  • Mock — ออบเจกต์ที่ตรวจสอบการโต้ตอบ: เมธอดใดถูกเรียก ด้วยอาร์กิวเมนต์ใด และกี่ครั้ง
  • Mockito — ไลบรารียอดนิยมที่สุดสำหรับสร้าง Mock ในโปรเจกต์ Java และ Android
  • MockK — ทางเลือกแทน Mockito สำหรับ Kotlin ที่รองรับ coroutine และ sealed class โดยกำเนิด
  • Behavior verification — ความแตกต่างหลักระหว่าง Mock และ Stub: Mock ตรวจสอบพฤติกรรม ไม่ใช่สถานะ
  • Over-mocking — แอนติแพทเทิร์นหลัก: ควรใช้ mock สำหรับการพึ่งพาภายนอกเท่านั้น

Mock คืออะไร?

Mock คือออบเจกต์ที่สร้างโดยเฟรมเวิร์ก mocking (Mockito, MockK, EasyMock) ซึ่งจำลองอินเทอร์เฟซหรือคลาส และบันทึกการเรียกเมธอดทั้งหมด นักพัฒนากำหนดความคาดหวัง: เมธอด X จะถูกเรียกด้วยอาร์กิวเมนต์ Y และจะคืนค่า Z หลังจากการทดสอบดำเนินการ Mock จะตรวจสอบว่าความคาดหวังตรงกับการเรียกจริงหรือไม่

คำนี้มาจาก อุปลักษณ์การแสดงละคร ของ Test Doubles: Mock คือ “ผู้เลียนแบบ” ที่ไม่เพียงยืนบนเวที (เหมือน Dummy) แต่แสดงบทบาทและตรวจสอบว่าการโต้ตอบกับมันถูกต้องหรือไม่ หากโค้ดที่ทดสอบไม่ได้เรียกเมธอดที่ Mock คาดหวัง หรือเรียกด้วยอาร์กิวเมนต์ที่ไม่ถูกต้อง — การทดสอบล้มเหลวพร้อมข้อความความคาดหวังที่ถูกละเมิด

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

Mock ถูกสร้างขึ้นผ่านโรงงานของเฟรมเวิร์ก: mockk<MyInterface>() หรือ Mockito.mock(MyClass.java) เฟรมเวิร์กสร้างออบเจกต์พร็อกซีที่สกัดกั้นการเรียกเมธอดทั้งหมด แต่ละการเรียกจะถูกเปรียบเทียบกับความคาดหวังที่กำหนดไว้ล่วงหน้า หากการเรียกตรงกับความคาดหวัง — ค่าที่ระบุจะถูกคืนค่า หากไม่ — Mock จะคืนค่าเริ่มต้นหรือโยนข้อยกเว้น ขึ้นอยู่กับการกำหนดค่า

เมื่อใดจำเป็นต้องใช้ Mock

Mock จำเป็นเมื่อโค้ดที่ทดสอบโต้ตอบกับคอมโพเนนต์ที่มีผลข้างเคียง: การส่งข้อมูลไปยังเซิร์ฟเวอร์ การเขียนลงฐานข้อมูล การบันทึก log analytics การนำทาง การแสดงไดอะล็อกของระบบ หากไม่มี Mock การโต้ตอบเหล่านี้ไม่สามารถตรวจสอบได้โดยไม่ต้องเรียกใช้โครงสร้างพื้นฐานจริง ตาม Google Testing Blog Mock เป็นวิธีเดียวที่จะตรวจสอบว่าแอปพลิเคชันส่งอีเวนต์ analytics จริงหรือไม่โดยไม่ต้องเปิดเซิร์ฟเวอร์ทดสอบ

Mock vs Stub: การเปรียบเทียบโดยละเอียด

ความแตกต่างระหว่าง Mock และ Stub เป็นหนึ่งในหัวข้อที่มีการถกเถียงมากที่สุดในการทดสอบ ทั้งสองประเภทแทนที่การพึ่งพาจริง แต่ด้วยวิธีที่แตกต่างกันโดยพื้นฐาน

เกณฑ์MockStub
คำถามหลักเมธอดถูกเรียกหรือไม่?ผลลัพธ์ใดถูกคืนค่า?
การตรวจสอบพฤติกรรม (verify)สถานะ (assert)
การคืนข้อมูลไม่บังคับบังคับ
ตัวอย่างverify(analytics).logEvent(“click”)assertEquals(5, repository.getCount())
เมื่อใดควรใช้ผลข้างเคียงการคืนข้อมูล

กฎปฏิบัติ: Mock หรือไม่

การทดสอบง่ายๆ เพื่อตัดสินใจ: ถามตัวเองว่า “ถ้าฉันลบบรรทัดโค้ดนี้ การทดสอบจะล้มเหลวหรือไม่?” หากการทดสอบตรวจสอบค่าที่คืน — คุณจำเป็นต้องใช้ Stub (การตรวจสอบแบบ assert) หากการทดสอบตรวจสอบว่าโค้ดเรียกเมธอดด้วยอาร์กิวเมนต์ที่ถูกต้อง — คุณจำเป็นต้องใช้ Mock (การตรวจสอบแบบ verify) การแบ่งขั้วนี้เป็นไปตามแพทเทิร์น Command-Query Separation: เมธอดที่เปลี่ยนสถานะ (commands) จำเป็นต้องใช้ Mock; เมธอดที่คืนข้อมูล (queries) จำเป็นต้องใช้ Stub

Mockito vs MockK: การเปรียบเทียบไลบรารี

การเลือกระหว่าง Mockito และ MockK เป็นหนึ่งในการตัดสินใจแรกเมื่อตั้งค่าสแต็กการทดสอบสำหรับโปรเจกต์ Android ใน Kotlin ไลบรารีทั้งสองทำหน้าที่เดียวกัน แต่มีแนวทางที่แตกต่างกันต่อคุณสมบัติเฉพาะของ Kotlin

Mockito: คลาสสิกที่ผ่านการพิสูจน์

Mockito เป็นมาตรฐานโดยพฤตินัยสำหรับโปรเจกต์ Java เวอร์ชัน 5.x รองรับการ mock สำหรับคลาส final เมธอด static และคอนสตรัคเตอร์ ต้องขอบคุณ MockMaker ในตัว สำหรับโปรเจกต์ Kotlin Mockito ต้องการการตั้งค่าเพิ่มเติม: ส่วนขยาย mockito-kotlin สำหรับไวยากรณ์ที่ดีขึ้น mockito-inline สำหรับคลาส final Mockito ไม่รองรับ coroutine ของ Kotlin และฟังก์ชัน suspend โดยไม่มีอะแดปเตอร์เพิ่มเติม

MockK: แนวทาง Kotlin-First

MockK ถูกสร้างขึ้นสำหรับ Kotlin โดยเฉพาะ รองรับ coroutine (coEvery, coVerify) sealed class data class object singleton และฟังก์ชัน extension โดยกำเนิด ไวยากรณ์ของ MockK ใช้ DSL กับบล็อกแลมบ์ดา ซึ่งดูเป็นธรรมชาติในโค้ด Kotlin MockK ยังรองรับการ mock คุณสมบัติ (property mocking) โดยไม่ต้องตั้งค่าเพิ่มเติม — ซึ่งสำคัญสำหรับโปรเจกต์ Android ที่ใช้ LiveData StateFlow และ Delegates

kotlin
// Mockito + mockito-kotlin
val repository = mock<UserRepository>()
whenever(repository.getUser(1)).thenReturn(user)

// MockK
val repository = mockk<UserRepository>()
every { repository.getUser(1) } returns user

การเปรียบเทียบประสิทธิภาพ

การวัดประสิทธิภาพ (JVM Benchmark, 2024) แสดงให้เห็นว่า MockK สร้างออบเจกต์ mock เร็วกว่า Mockito 15–20% สำหรับโปรเจกต์ Kotlin เนื่องจากทำงานโดยตรงกับไบต์โค้ดของ Kotlin แทนที่จะเป็น Java Reflections สำหรับโปรเจกต์ที่มีการทดสอบหน่วยเป็นพันๆ ความแตกต่างของความเร็วในการ build อาจเห็นได้ชัด: MockK ประหยัดเวลา 30–60 วินาทีในการรันทดสอบทั้งหมดในโปรเจกต์ขนาดใหญ่

ตัวอย่างการทดสอบ Mock ใน Kotlin

มาดูสามสถานการณ์: การทดสอบ ViewModel ด้วยการพึ่งพา Mock การทดสอบ UseCase ด้วยการตรวจสอบการเรียก API และการทดสอบ coroutine ด้วย coVerify

ตัวอย่างที่ 1: ViewModel ด้วย Mock analytics

kotlin
class ProfileViewModelTest {
    private val analytics = mockk<AnalyticsService>()
    private val repo = mockk<UserRepository>()
    private val vm = ProfileViewModel(repo, analytics)

    fun `profile opened logs analytics event`() {
        every { analytics.logEvent("profile_opened") } returns Unit

        vm.onViewCreated()

        verify { analytics.logEvent("profile_opened") }
    }
}

ตัวอย่างที่ 2: UseCase ด้วยการตรวจสอบแบบอะซิงโครนัส

kotlin
class SendMessageUseCaseTest {
    private val api = mockk<MessagingApi>()
    private val useCase = SendMessageUseCase(api)

    fun `send message with correct payload`() = runTest {
        val message = Message(text = "Hello", userId = 42)

        coEvery { api.sendMessage(any()) } returns MessageResult.Sent("msg_1")

        val result = useCase.execute(message)

        coVerify {
            api.sendMessage(match {
                it.text == "Hello" && it.userId == 42
            })
        }
        assertTrue(result is MessageResult.Sent)
    }
}

ตัวอย่างที่ 3: การตรวจสอบอาร์กิวเมนต์ด้วย ArgumentCaptor

kotlin
class OrderUseCaseTest {
    private val api = mockk<OrderApi>()
    private val useCase = OrderUseCase(api)
    private val slot = slot<OrderRequest>()

    fun `order request contains correct items`() = runTest {
        coEvery { api.placeOrder(capture(slot)) } returns OrderResult.Placed("order_1")

        useCase.execute(listOf("item_a", "item_b"))

        assertEquals(2, slot.captured.items.size)
        assertEquals("item_a", slot.captured.items[0])
    }
}

แนวทางปฏิบัติที่ดีที่สุดสำหรับการทดสอบ Mock

การใช้ Mock อย่างมีประสิทธิภาพในการพัฒนาแอปมือถือต้องมีวินัย การละเมิดกฎเหล่านี้จะเปลี่ยนการทดสอบให้เป็นอุปสรรคที่เปราะบางซึ่งพังทุกครั้งที่มีการปรับโครงสร้างโค้ด

Mock เฉพาะขอบเขตภายนอกของแอปพลิเคชัน

กฎที่เคร่งครัด: ควรสร้าง Mock สำหรับการพึ่งพาที่ข้ามขอบเขตของแอปพลิเคชันเท่านั้น: ไคลเอนต์ API ฐานข้อมูล ระบบไฟล์ บริการระบบ (LocationManager, BluetoothAdapter, Camera) คลาสภายในของแอปพลิเคชัน — เอนทิตีโดเมน Value Objects ยูทิลิตี้ธรรมดา — ไม่ควรถูกแทนที่ด้วย Mock พฤติกรรมของพวกเขาจะถูกทดสอบผ่านออบเจกต์จริง

หนึ่ง assert / verify ต่อการทดสอบ

แต่ละการทดสอบควรมีการตรวจสอบเชิงตรรกะเพียงหนึ่งเดียว — verify (สำหรับ Mock) หรือ assert (สำหรับ Stub) อย่าผสมการตรวจสอบสถานะและพฤติกรรมในการทดสอบเดียว หากคุณต้องการตรวจสอบทั้งการเรียก API และผลลัพธ์ — ให้สร้างการทดสอบแยกกันสองรายการด้วยชื่อที่แตกต่างกัน กฎนี้เรียกว่า “หนึ่ง assert ต่อการทดสอบ” ย้อนกลับไปถึงคำแนะนำของ Kent Beck (2002)

  • ใช้ relaxUnitFun = true ใน MockK สำหรับเมธอดที่คืนค่า Unit — มิฉะนั้น Mock จะโยนข้อยกเว้นในการเรียกที่ไม่ระบุ
  • จำกัด verify เฉพาะการเรียกที่สำคัญ — อย่าตรวจสอบ getter และ setter ทุกตัว เพราะจะทำให้การทดสอบเปราะบาง
  • ใช้ ArgumentMatchers อย่างชาญฉลาด — any() ซ่อนรายละเอียดสำคัญหากอาร์กิวเมนต์สำคัญต่อตรรกะทางธุรกิจ
  • อย่าใช้ verifyNoMoreInteractions มากเกินไป — เมธอดนี้ทำให้การทดสอบแข็งทื่อโดยไม่จำเป็นต่อการเปลี่ยนแปลงใดๆ ในโค้ดการผลิต
  • ใช้ @MockkAnnotations สำหรับการเริ่มต้นออบเจกต์ Mock อัตโนมัติ — ซึ่งลดโค้ดซ้ำซากและปรับปรุงความสามารถในการอ่าน

เทคนิคการทดสอบ Mock ขั้นสูง

นอกเหนือจากการ mock พื้นฐาน ยังมีเทคนิคขั้นสูงที่แก้ไขงานเฉพาะในการพัฒนาแอปมือถือ: การทดสอบมัลติเธรด การตรวจสอบสถานะ Flow และการ mock บางส่วนของออบเจกต์จริง

Mock บางส่วนด้วย spyK

Spy (หรือ mock บางส่วน) ช่วยให้สร้างออบเจกต์ที่มอบหมายการเรียกไปยังการใช้งานจริง แต่ให้แทนที่เมธอดแต่ละรายการได้ ใน MockK spyk ถูกสร้างขึ้นจากอินสแตนซ์คลาสจริง: val repo = spyk(InMemoryUserRepository()) การเรียกที่มีความคาดหวังที่กำหนดผ่าน every จะผ่าน Mock; ส่วนที่เหลือจะผ่านออบเจกต์จริง Spy มีประโยชน์โดยเฉพาะสำหรับการทดสอบโค้ดเก่าที่ยังไม่ได้ใช้การฉีดการพึ่งพา และคุณต้องการแทนที่เพียงเมธอดเดียว

การทดสอบ StateFlow ด้วย Turbine

ในโปรเจกต์ Android สมัยใหม่ที่ใช้ Jetpack Compose ViewModel เปิดเผยสถานะผ่าน StateFlow MockK อนุญาตให้ mock การพึ่งพา Flow และไลบรารี Turbine ทำให้การตรวจสอบการปล่อยค่าทำได้ง่ายขึ้น รูปแบบคลาสสิก: MockK สำหรับ UseCase ที่คืนค่า Flow, Turbine สำหรับตรวจสอบการปล่อยค่าของ ViewModel สแต็กนี้ได้รับการแนะนำโดยเอกสาร Android Testing (Google, 2024) สำหรับโปรเจกต์ที่ใช้ Kotlin Coroutines

kotlin
class SearchViewModelTest {
    private val searchUseCase = mockk<SearchUseCase>()
    private val vm = SearchViewModel(searchUseCase)

    fun `search emits results`() = runTest {
        coEvery { searchUseCase.search("android") } returns
            flowOf(SearchResult.Success(listOf(Item("Android TDD"))))

        vm.search("android")

        vm.state.test {
            val state = awaitItem()
            assertTrue(state.items.isNotEmpty())
            cancelAndIgnoreRemainingEvents()
        }
    }
}

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

Mock แตกต่างจาก Mockito อย่างไร?

Mock เป็นแนวคิด ประเภทของ Test Double ที่ตรวจสอบพฤติกรรม Mockito เป็นไลบรารีสำหรับสร้างออบเจกต์ Mock ใน Java และ Android ไลบรารีอื่นๆ: MockK (Kotlin), EasyMock (Java), Cuckoo (iOS)

Mock ทำงานกับ coroutine ของ Kotlin อย่างไร?

สำหรับการทดสอบฟังก์ชัน suspend ด้วย Mock ให้ใช้ MockK (coEvery / coVerify) หรือ Mockito กับ mockito-kotlin MockK รองรับ coroutine โดยกำเนิด: coEvery กำหนดพฤติกรรมของฟังก์ชัน suspend, coVerify ตรวจสอบการเรียกภายใน coroutine การเรียก suspend ทั้งหมดต้องดำเนินการภายใน runTest (kotlinx-coroutines-test)

Mock สามารถคืนค่าที่แตกต่างกันในการเรียกซ้ำได้หรือไม่?

ได้ ใน MockK ใช้ returnsMany: every { api.getData() } returnsMany listOf(response1, response2) ใน Mockito — สายโซ่ของ thenReturn(value1).thenReturn(value2) ซึ่งมีประโยชน์สำหรับการทดสอบพฤติกรรมที่มีการเรียกตามลำดับซึ่งคืนค่าที่แตกต่างกัน

จะล้างสถานะ Mock ระหว่างการทดสอบได้อย่างไร?

ใน MockK ใช้คำอธิบายประกอบ @MockK ด้วย relaxed = true และเรียก clearMocks(mock) ในเมธอด @After ใน MockitoMockito.reset(mock) แนวทางปฏิบัติที่ดีที่สุด: สร้าง Mock ใหม่สำหรับแต่ละการทดสอบผ่าน @Before เพื่อกำจัดการแทรกแซงระหว่างการทดสอบ

Mock จัดการ sealed class ใน Kotlin อย่างไร?

MockK ทำงานได้อย่างถูกต้องกับ sealed class: every { useCase() } returns Result.Success(data) Mockito ไม่รองรับ sealed class โดยตรง ต้องใช้วิธีแก้ไข นี่คือเหตุผลหนึ่งที่แนะนำ MockK สำหรับโปรเจกต์ Kotlin แทน Mockito

สรุป

  • Mock — ประเภท Test Double ที่ตรวจสอบพฤติกรรม (verify) ไม่ใช่สถานะ (assert) ของการพึ่งพา
  • Mockito — มาตรฐานสำหรับ Java/Android, MockK — ตัวเลือก Kotlin-First ที่รองรับ coroutine และ sealed class
  • กฎหลัก: Mock สำหรับขอบเขตภายนอก (เครือข่าย, DB, บริการระบบ), ออบเจกต์จริงสำหรับคลาสภายใน
  • Over-mocking — แอนติแพทเทิร์นหลัก: การแทนที่การพึ่งพามากเกินไปทำให้การทดสอบเปราะบางและไร้ประโยชน์
  • หนึ่งการทดสอบ — หนึ่งการตรวจสอบเชิงตรรกะ: verify สำหรับ Mock หรือ assert สำหรับ Stub แต่ไม่ใช่ทั้งสองอย่างในการทดสอบเดียวกัน
  • ArgumentCaptor / slot — วิธีที่ถูกต้องในการตรวจสอบอาร์กิวเมนต์การเรียก Mock แทนการใช้ any() แบบไม่เจาะจง
  • MockK ได้รับคำแนะนำสำหรับโปรเจกต์ Kotlin: coEvery และ coVerify ทำงานโดยกำเนิดกับ coroutine โดยไม่ต้องใช้อะแดปเตอร์เพิ่มเติม

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

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

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

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