Mockito เป็นเฟรมเวิร์กโอเพนซอร์สสำหรับสร้างวัตถุ mock ในการทดสอบหน่วยของ Java และ Kotlin ซึ่งช่วยให้โค้ดที่กำลังทดสอบสามารถแยกออกจากการพึ่งพาภายนอกได้ ด้วยความช่วยเหลือนี้ นักพัฒนาจะแทนที่พื้นที่เก็บจริง, ไคลเอนต์ API และฐานข้อมูลด้วย stubs ที่ควบคุมได้โดยมีพฤติกรรมที่กำหนดไว้ล่วงหน้า ตาม Mockito.org, ไลบรารีถูกใช้ในมากกว่า 60% ของโปรเจค Java ที่ใช้การทดสอบหน่วย
ประเด็นสำคัญ
Mockito เป็นไลบรารีโอเพนซอร์สสำหรับสร้างวัตถุ mock (stub) ในการทดสอบหน่วยสำหรับ Java, Kotlin และภาษ JVM อื่นๆ ต่างจาก JUnit ที่รับผิดชอบการดำเนินการทดสอบ Mockito แก้ปัญหาการ แยกแยะ — มันแทนที่การพึ่งพาจริงของคลาสที่กำลังทดสอบด้วยวัตถุที่สามารถทำนายได้ล่วงหน้า
หากไม่มี mock, การทดสอบเมธอดที่เข้าถึงฐานข้อมูลหรือ API ภายนอกต้องตั้งค่าสภาพแวดล้อมจริง — การเผยแพร่ฐานข้อมูล, การเริ่มเซิร์ฟเวอร์ Mockito แทนที่การพึ่งพาเหล่านี้ด้วยวัตถุที่มีพฤติกรรมตามที่กำหนด: เมธอด repository.findById(1) จะส่งคืนวัตถุ User ที่กำหนดไว้เสมอโดยไม่ต้องเข้าถึงฐานข้อมูล
สถาปัตยกรรมของ Mockito ขึ้นอยู่กับรูปแบบ Proxy (สำหรับอินเทอร์เฟซและคลาส) ไลบรารีจะสร้างคลาสย่อยหรือพร็อกซีสำหรับประเภทที่ระบุและสกัดกั้นการเรียกเมธอดทั้งหมด, ส่งคืนค่าเริ่มต้นหรือค่าที่กำหนดผ่าน when().thenReturn()
หลักการทำงานของ Mockito สร้างขึ้นจากการดำเนินการพื้นฐานสามอย่าง: การสร้าง mock, การกำหนดพฤติกรรม (stubbing) และการตรวจสอบการเรียก (verification) แต่ละการดำเนินการใช้เมธอดสแตติกจากคลาส org.mockito.Mockito — คลาสที่ถูกดาวน์โหลดมากที่สุดในระบบนิเวศของ Java ตามสถิติของ Maven Central การเรียกเมธอดของ mock ทั้งหมดจะถูกบันทึกไว้ในหน่วยความจำ, ซึ่งช่วยให้สามารถตรวจสอบภายหลังผ่าน verify
การทดสอบทั่วไปกับ Mockito ประกอบด้วยสามเฟส: Arrange — การสร้าง mocks และการกำหนด stubs ผ่าน when().thenReturn(), Act — การเรียกเมธอดที่กำลังทดสอบ, Assert — การตรวจสอบผลลัพธ์ผ่าน assertEquals และ verify(mock) วิธีการนี้เรียกว่า AAA (Arrange-Act-Assert)
ลองดูการทดสอบง่ายๆ ที่ Mockito แทนที่ repository ของผู้ใช้ เมธอด when().thenReturn() จะกำหนดค่า mock ให้การเรียก findById ส่งคืนวัตถุ User ที่เตรียมไว้ล่วงหน้า
// สร้าง mock ของ repository
UserRepository mockRepo = mock(UserRepository.class);
// กำหนดพฤติกรรม: findById(1) ส่งคืนผู้ใช้
when(mockRepo.findById(1)).thenReturn(new User("Alice"));
// ตรวจสอบว่าเมธอดถูกเรียกจริง
User result = mockRepo.findById(1);
assertEquals("Alice", result.getName());
verify(mockRepo).findById(1);
Mockito มีสองวิธีในการสร้าง mocks: เมธอดสแตติก mock(Class) และแอนโนเทชั่น @Mock กับการเริ่มต้นผ่าน MockitoAnnotations.openMocks() วิธีแรกกะทัดรัดสำหรับหนึ่งหรือสอง mocks, วิธีที่สองสะดวกเมื่อมีการพึ่งพาจำนวนมาก — แอนโนเทชั่นช่วยลดโค้ด boilerplate
เมธอด mock() รับคลาสและส่งคืนวัตถุ stub ที่สามารถกำหนดค่าผ่าน when().thenReturn() เมธอดที่ไม่ได้กำหนดค่าทั้งหมดจะส่งคืนค่าเริ่มต้น: 0 สำหรับตัวเลข, false สำหรับ boolean, null สำหรับวัตถุ
ApiClient apiClient = mock(ApiClient.class);
Database database = mock(Database.class);
แอนโนเทชั่น @Mock ร่วมกับ @ExtendWith(MockitoExtension.class) จะสร้าง mocks โดยอัตโนมัติสำหรับทุกๆ ฟิลด์ของคลาสทดสอบ MockitoExtension มีหน้าที่รับผิดชอบการเริ่มต้นก่อนแต่ละการทดสอบ
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void getUserShouldReturnUserFromRepo() {
when(userRepository.findById(1)).thenReturn(new User("Alice"));
User result = userService.getUser(1);
assertEquals("Alice", result.getName());
}
}
Stubbing คือกระบวนการกำหนดว่าเมธอดของ mock ควรจะส่งคืนอะไรเมื่อถูกเรียกด้วยอาร์กิวเมนต์เฉพาะเจาะจง ไวยากรณ์พื้นฐาน: when(mock.method(args)).thenReturn(value) สำหรับสถานการณ์ต่างๆ, Mockito มีตัวแปรเมธอด then หลายแบบ
| เมธอด | วัตถุประสงค์ |
|---|---|
| thenReturn(value) | ส่งคืนค่าที่ระบุเสมอ |
| thenThrow(exception) | เหตุข้อยกเว้นเมื่อเรียก |
| thenAnswer(answer) | คำนวณค่าที่ส่งคืนแบบไดนามิก |
| thenCallRealMethod() | เรียกเมธอดจริง (mock บางส่วน) |
เมื่อค่าที่ส่งคืนขึ้นอยู่กับอาร์กิวเมนต์ของการเรียก, จะใช้ thenAnswer กับ lambda ซึ่งมีประโยชน์ในการจำลองการทำงานกับข้อมูลจริง — ตัวอย่างเช่น, การสร้าง ID โดยอิงตามวัตถุที่ส่งมา
when(repository.save(any())).thenAnswer(invocation -> {
User user = invocation.getArgument(0);
user.setId(42);
return user;
});
Verify เป็นคุณสมบัติพิเศษของ Mockito ที่ไลบรารีวัตถุ mock รุ่นเก่าๆ (EasyMock, jMock) ไม่มี
Verify ทำให้การทดสอบน่าเชื่อถือมากขึ้นเพราะมันตรวจสอบไม่เฉพาะค่าที่ส่งคืนแต่รวมถึงผลกระทบข้างเคียง — การเรียกเมธอดที่ไม่ส่งคืนผลลัพธ์ (เมธอด void) เมธอด verify(mock).methodName(args) ตรวจสอบว่าเมธอดเฉพาะของ mock ถูกเรียกด้วยอาร์กิวเมนต์ที่ระบุหรือไม่ ซึ่งช่วยให้สามารถทดสอบไม่เฉพาะผลลัพธ์แต่รวมถึงกระบวนการ — ข้อเท็จจริงของการเข้าถึงการพึ่งพา
โดยค่าเริ่มต้น, verify ตรวจสอบว่าเมธอดถูกเรียกครั้งเดียวพอดี หากต้องการจำนวนที่แตกต่าง, จะใช้ times(n), atLeast(n), never() และตัวแก้ไขอื่นๆ จากคลาส Mockito
// ตรวจสอบจำนวนการเรียก
verify(repository, times(3)).save(any());
verify(repository, never()).delete(any());
verify(repository, atLeastOnce()).findById(1);
// ตรวจสอบลำดับการเรียก
InOrder inOrder = inOrder(repository);
inOrder.verify(repository).save(any());
inOrder.verify(repository).flush();
เมื่อต้องตรวจสอบว่าเมธอดถูกเรียกด้วยวัตถุใด, จะใช้ ArgumentCaptor ซึ่งจับค่าอาร์กิวเมนต์ระหว่างการเรียกและช่วยให้ตรวจสอบฟิลด์ของมันทีละตัว ArgumentCaptor มีประโยชน์โดยเฉพาะเมื่อโค้ดที่กำลังทดสอบสร้างวัตถุภายในและส่งต่อไปให้การพึ่งพา — คุณไม่สามารถตรวจสอบวัตถุนั้นด้วยวิธีอื่น
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
verify(repository).save(captor.capture());
assertEquals("Alice", captor.getValue().getName());
@Mock และ @InjectMocks เป็นสองแอนโนเทชั่นหลักของ Mockito ที่ช่วยลดโค้ด boilerplate ได้อย่างมาก @Mock สร้าง mock สำหรับฟิลด์, และ @InjectMocks ฉีด mocks ทั้งหมดจากคลาสทดสอบเข้าไปในวัตถุที่กำลังทดสอบผ่านคอนสตรักเตอร์, setter หรือฟิลด์
กลไกของ @InjectMocks จะพยายามฉีดการพึ่งพาตามลำดับดังนี้: คอนสตรักเตอร์ที่มีจำนวนอาร์กิวเมนต์มากที่สุด, setter ตามประเภท, ฟิลด์ส่วนตัว หากไม่มีวิธีใดทำงานได้, วัตถุจะเหลือการพึ่งพาเป็น null และการทดสอบจะล้มเหลวด้วย NullPointerException
สิ่งสำคัญที่ต้องเข้าใจ: @InjectMocks ไม่ได้วิเคราะห์ประเภทของฟิลด์ — มันจะแทนที่ mock ใดก็ตามที่เข้ากันได้ตามประเภท หากคลาสมีสองฟิลด์ที่ประเภทเดียวกัน, Mockito อาจฉีด mock ผิด ในกรณีเช่นนี้, แนะนำให้ใช้คอนสตรักเตอร์ที่ชัดเจนกับพารามิเตอร์ mock
ในการพัฒนา Android, Mockito ถูกใช้ร่วมกับ JUnit เพื่อทดสอบ ViewModel, Repository และ UseCase เนื่องจากคลาสเหล่านี้ทำงานบน JVM โดยไม่มีบริบท Android, Mockito จะแทนที่การพึ่งพาของพวกเขา — Room DAO, Retrofit API, SharedPreferences — ด้วย stubs ที่มีพฤติกรรมที่สามารถทำนายได้ล่วงหน้า
เพื่อเพิ่ม Mockito ในโปรเจค Android, เพียงเพิ่มการพึ่งพา mockito-core หรือ mockito-inline (แบบหลังรองรับการทำ mock คลาส final และเมธอดสแตติก) รุ่น 5.12.0 (2024) รวมถึงการสนับสนุน Java 21 และการผนวกรวม JUnit 5 ที่ดีขึ้น
// build.gradle.kts (module)
dependencies {
testImplementation("org.mockito:mockito-core:5.12.0")
testImplementation("org.mockito:mockito-junit-jupiter:5.12.0")
}
ก่อนหน้านี้, การทำ mock เมธอดสแตติกและคอนสตรักเตอร์จำเป็นต้องใช้ PowerMock — ส่วนขยายที่ทำงานผ่านการเปลี่ยนแปลงไฟล์โค้ด ตั้งแต่ Mockito 5.x กับ mockito-inline, ความสามารถนี้ถูกรวมอยู่ในตัวโดยตรง: mockStatic(ClassName.class) ช่วยให้สามารถทำ mock เมธอดสแตติกได้โดยไม่ต้องใช้ไลบรารีเพิ่มเติม
สถานการณ์ทั่วไป: ViewModel เรียกเมธอดของ repository และแปลงผลลัพธ์เป็นสถานะ UI Mockito แทนที่ repository, และการทดสอบจะตรวจสอบว่า ViewModel จัดการกับการตอบสนองที่สำเร็จและข้อผิดพลาดได้อย่างถูกต้อง เมื่อใช้ Clean Architecture, mocks จะถูกสร้างขึ้นสำหรับแต่ละชั้น: DataSource, Repository และ UseCase — ซึ่งช่วยให้สามารถทดสอบแต่ละชั้นได้อย่างโดดเดี่ยว
คำถามที่พบบ่อย
Mockito เป็นไลบรารีสำหรับ Java และ Kotlin ที่ใช้ proxy และ reflection MockK เป็นไลบรารี Kotlin-first ที่รองรับ coroutines, ฟังก์ชันส่วนขยาย และคลาส final โดยไม่ต้องกำหนดค่าเพิ่มเติม
ตั้งแต่ Mockito 2.1, การทำ mock คลาส final รองรับผ่าน opt-in ในรุ่น 5.x (mockito-inline), นี้เปิดใช้โดยค่าเริ่มต้น เพียงเพิ่มการพึ่งพา mockito-inline และใช้เมธอด mock() มาตรฐาน
Spy คือ mock บางส่วนที่โดยค่าเริ่มต้นจะเรียกเมธอดจริงแต่อนุญาตให้เขียนทับบางส่วนผ่าน when().thenReturn() Spy มีประโยชน์สำหรับการทดสอบโค้ด legacy เมื่อไม่สามารถเขียนคลาสใหม่ทั้งหมดได้
thenReturn จะส่งคืนค่าเดียวกันเสมอ, โดยไม่ขึ้นอยู่กับอาร์กิวเมนต์ thenAnswer คำนวณค่าที่ส่งคืนโดยอิงตามการเรียกใช้ — อาร์กิวเมนต์, ตัว mock เอง และสถานะ สำหรับการตอบสนองแบบไดนามิก, ให้ใช้ thenAnswer เสมอ
Verify ตรวจสอบไม่เฉพาะผลลัพธ์แต่รวมถึงกระบวนการ — ข้อเท็จจริงของการเข้าถึงการพึ่งพา ซึ่งมีความสำคัญสำหรับบริการที่ต้องบันทึกข้อมูลหรือส่งการแจ้งเตือน หากไม่มี verify, การทดสอบจะไม่ตรวจพบว่าเมธอดไม่ได้เรียก save() หรือ send()
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ