การเริ่มใช้ Mockito (@Mock กับ @InjectMocks) ใน Spring Boot
Tags: mock, mockito, springboot, testdouble
Published: 2020-04-19 04:56:13

---
title: การเริ่มใช้ Mockito (@Mock กับ @InjectMocks) ใน Spring Boot
published: true
description: การเริ่มใช้ Mockito (@Mock กับ @InjectMocks) ใน Spring Boot ตั้งแต่ยังไม่ได้เอามาใช้ จนถึงการเอามาใช้เพื่อทดสอบพฤติกรรม และจำลองพฤติกรรมการทำงานของ dependency ในโค้ดได้
tags: mock, mockito, spring boot, testdouble
---

ลอง search ใน Google แล้วพบว่ามีหลายบทความที่เขียนเกี่ยวกับเรื่องนี้เยอะอยู่เหมือนกัน ไล่ตามอ่านหลายบทความอยู่จนพอที่จะทำ minimal working example เกี่ยวกับการ Mock ใน Spring Boot โดยใช้ Mockito (แค่ @Mock กับ @InjectMocks) ได้แล้ว ดีใจ~ 🤩🎉

เผื่อใครอยากลองทำตามก็ไปใช้ [Spring Initializr](https://start.spring.io/) สร้างโปรเจคมาก่อน เลือกเป็น Maven หรือ Gradle ก็ได้นะ ส่วนภาษาก็จริงๆ เลือกอะไรก็ได้ ไม่ว่าจะเป็น Java หรือ Kotlin หรือ Groovy แต่ในบทความจะเป็น Java นะครับ (เหตุผล? ตอนที่เขียนบทความนี้ผมรู้จัก syntax ของ Java อยู่ภาษาเดียวครับ 😂) สร้างเสร็จแล้วก็น่าจะได้ ZIP ไฟล์มา เราก็เอาไป import เข้า IDE ตัวที่ถนัดของเรา ไปเริ่มเขียนโค้ดกันเลย

### แบบยังไม่ได้ใช้ Mockito

สมมุติว่าเรามีคลาสแบบง่ายๆ อยู่ 2 คลาส

```java
package team.bars.mockito;

public class Bear {
    public String roar() {
        return "Hello";
    }
}

public class BearService {
    public String say() {
        Bear bear = new Bear();
        return bear.roar();
    }
}
```

ตัว `BearService` แค่สร้าง `bear` ขึ้นมาแล้วส่งค่าจาก method ที่ชื่อ `roar` ออกไป เวลาที่เราเขียนเทสก็จะประมาณนี้

```java
package team.bears.mockito;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class BearServiceTest {
    @Test
    public void testItShouldReturnHelloFromBear() {
        BearService bearService = new BearService();
        String actual = bearService.say();
        assertEquals("Hello", actual);
    }
}
```

ก็ดูปกติไม่มีอะไรเนอะ แต่ว่าแบบนี้มีจุดที่เราสามารถปรับปรุงให้ดีขึ้นได้คือ

1. คลาส `BearService` มีการเรียก `Bear` ที่เป็น dependency ข้างใน เสมือนกับว่าเทสนี้ได้ไปทดสอบตัว `Bear` ไปด้วยเลยโดยปริยาย ซึ่งความต้องการจริงๆ แล้วเราอาจจะอยากทดสอบแค่ตัว `BearService` พอ
1. เรามองไม่เห็นว่า `BearService` ได้ไปเรียก `Bear` จริงๆ หรือเปล่าตามที่เราตั้งใจไว้
1. ถ้าเป็นกรณีที่ `say` ของ `BearService` ไปเรียก API เวลาที่เรารันเทสแล้ว มันก็จะไปยิง API จริงๆ ซึ่งเราคงไม่อยากให้เป็นแบบนั้น

### มาลองใช้ Mockito (@Mock กับ @InjectMocks) กัน

ก่อนอื่นเราจะต้องไปเพิ่ม Mockito ให้เป็น dependency ก่อน ถ้าใครใช้ [Gradle](https://gradle.org/) ก็ให้ไปเพิ่ม dependency ที่ใช้สำหรับตอน compile ตัวเทส (ไม่เอาไปใช้บน production) ใน `dependencies` ที่ไฟล์ `build.gradle` ประมาณนี้

```groovy
dependencies {
	...
	testCompile group: 'org.mockito', name: 'mockito-core', version: '3.3.3'
}
```

ถ้าใครใช้ [Maven](http://maven.apache.org/) ก็ให้เพิ่ม dependency ใน `dependencies` ที่ไฟล์ `pom.xml` ตามนี้

```xml
<dependencies>
    ...
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>3.3.3</version>
        <scope>test</scope>
    </dependency>
</dependencies>
```

รู้ได้อย่างไรว่าต้องเขียน dependency แบบนี้? ดูจาก [Maven Repository](https://mvnrepository.com/artifact/org.mockito/mockito-core/3.3.3) จ้า 😆

ต่อไปเราจะไปแก้ที่ `BearService` ก่อน โดยแทนที่เราจะ instantiate ตัว `bear` ขึ้นมาเอง เราจะใช้เทคนิค dependency injection เพื่อที่เราจะได้ไม่ต้องมา instantiate ใน `BearService` เอง ซึ่งใน Spring มี annotation ที่ชื่อ [@Autowired](https://www.baeldung.com/spring-autowire) มาช่วยให้ชีวีตเราง่ายขึ้น โค้ดของ `BearService` จะได้ตามนี้

```java
package team.bears.mockito;

import org.springframework.beans.factory.annotation.Autowired;

public class BearService {
    @Autowired
    Bear bear;

    public String say() {
        return bear.roar();
    }
}
```

จากนั้นเราก็จะไปแก้เทส `BearServiceTest` กัน เราอยากจะ mock ตัว `Bear` เพื่อที่เราจะได้ทดสอบแค่ส่วนของ `BearService` เราก็จะแก้โค้ดตามนี้

```java
package team.bears.mockito;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.Assertions.assertEquals;

@ExtendWith(MockitoExtension.class)
public class BearServiceTest {
    @Mock
    Bear bear;

    @InjectMocks
    BearService bearService;

    @Test
    public void testItShouldReturnHelloFromBear() {
        String actual = bearService.say();
        assertEquals("Hello", actual);
    }
}
```

ถ้าเราใช้ JUnit 5 (มีคำว่า Jupiter) เวลาเราจะใช้ Mockito เราก็ใส่ `@ExtendWith(MockitoExtension.class)` ไว้บนคลาส ถ้าใครใช้ JUnit เวอร์ชั่นต่ำกว่านี้ ก็ให้ลง JUnit 5 ครับ อ่านไปอ่านบทความ [ใช้ JUnit 5 + Mockito บน Spring Boot](https://medium.com/@phayao/%E0%B9%83%E0%B8%8A%E0%B9%89-junit-5-mockito-%E0%B8%9A%E0%B8%99-spring-boot-6877b5b1b226) กันต่อได้

ทีนี้ `@Mock` กับ `@InjectMock` เนี่ยมันคืออะไรนะ? ผมขอใช้ประโยคง่ายๆ ละกัน

1. ตัว `@Mock` เป็นการบอกว่ามันคือ object ที่เราจะ mock นะ
1. ตัว `@InjectMocks` จะเป็นบอกว่า object ที่เราแปะหัวเนี่ย จะมีการ inject mock เข้าไปนะ

เสร็จแล้วก็ให้ลองรันเทสดูครับ มันควรจะ fail! 💥 เราจะเห็น error ประมาณนี้

```
expected: <Hello> but was: <null>
```

ดีใจได้เลยครับ มันแปลว่าเรา mock สำเร็จแล้ว 🎉 ทีนี้เราอยากแค่จะทดสอบนะ ว่า `bear` ที่เรา mock ไว้มันจะโดยเรียกจริงเปล่า? ต้องเขียนโค้ดอย่างไรนะ? จัดไปตามนี้ครับ เราจะใช้ `verify` กับ `times` จาก Mockito

```java
package team.bears.mockito;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.times;

@ExtendWith(MockitoExtension.class)
public class BearServiceTest {
    @Mock
    Bear bear;

    @InjectMocks
    BearService bearService;

    @Test
    public void testItShouldReturnHelloFromBear() {
        bearService.say();
        verify(bear, times(1)).roar();
    }
}
```

แล้วถ้าเราอยากจะ Stub ตัว `bear` สามารถทำได้ด้วยหรือเปล่า? ทำได้ครับ จัดไปตามนี้ เราจะใช้ `when` จาก Mockito

```java
package team.bears.mockito;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class BearServiceTest {
    @Mock
    Bear bear;

    @InjectMocks
    BearService bearService;

    @Test
    public void testItShouldReturnHelloFromBear() {
        when(bear.roar()).thenReturn("Grrrrr!");

        String actual = bearService.say();
        assertEquals("Grrrrr!", actual);

        verify(bear, times(1)).roar();
    }
}
```

โค้ดทั้งหมดที่ใช้ในบทความนี้อยู่ที่ [GitHub](https://github.com/zkan/hello-springboot/tree/master/mockito) นะครับ ลองเอาไปเล่นกันดู ตรงไหนปรับให้ดีขึ้นได้ ช่วยเปิด pull request มาให้ด้วยนะ 🤣

หลังจาก[โพสต์ลง Facebook](https://www.facebook.com/zkan.cs/posts/3162545270443957) ไป [พี่ปุ๋ย](http://www.somkiat.cc/)มาให้คำแนะนำเกี่ยวกับ `@InjectMocks` ว่า

![พี่ปุ๋ยให้คำแนะนำเรื่องการใช้ @InjectMocks](https://dev-to-uploads.s3.amazonaws.com/i/tuc837q99vinc7wbqy20.png)

🙏 กราบขอบคุณพี่ปุ๋ยมา ณ ที่นี้ด้วยครับ ถ้าใครสนใจรายละเอียดเพิ่มเติมก็ลองไปอ่าน [InjectMocks doc](https://javadoc.io/static/org.mockito/mockito-core/3.3.3/org/mockito/InjectMocks.html) กันดูนะ

ทีนี้ผมขอมาแก้คลาส `BearService` สักเล็กน้อย สุดท้ายแล้วจะได้ตามนี้

```java
package team.bears.mockito;

public class BearService {
    private Bear bear;

    public BearService(Bear bear) {
        this.bear = bear;
    }

    public String say() {
        return this.bear.roar();
    }
}
```

ที่นี้ก็น่าจะชัดเจนแล้วว่าตัว mock จะถูก inject เข้ามาที่ default constructor ของ `BearService` 🤓