【Spring Boot】JunitでControllerの単体テストを行う

【Spring Boot】 JunitでControllerの 単体テストを行う バックエンド
【Spring Boot】 JunitでControllerの 単体テストを行う
スポンサーリンク

はじめに

ぴんくうさぎ
ぴんくうさぎ

Controllerの実装ができたけど、テストコードが全然書けません!

みどりがめ
みどりがめ

OK!今回はControllerクラスのテストコードの書き方を勉強していこう!

今回はJUnit5を使用したControllerクラスのテストコードの記述方法を紹介します。
なお、実装はSpring Bootで行っています。

Junitの基本的な使い方がわからない方は以下の記事を参考にしてください!

また、私自身がJUnitを学習する際には、以下の書籍にお世話になりました。
JUnitの参考書としては頭一つ抜けて人気で王道書籍です。
基本的なところから丁寧に解説されていて、非常に分かりやすいです。これからJUnitを学習する方におすすめです。

モック(Mock)とは?

テストコードの前に前提知識の確認です。ご存じの方はスキップしてください!

モック(Mock)は一言でいうと「テストに必要な部品を作成したもの」です。

テスト対象のクラスが他のクラスのメソッドの戻り値を使用している場合、モックを使用することで他のクラスの影響を受けずに戻り値を設定することができます。

具体例を一つ挙げます。テスト対象をControllerクラスとします。
ControllerクラスはServiceクラスの戻り値を使用します。
この時にServiceクラスをモック化します。すると、Seriviceクラスの戻り値を自在に設定できるので、Serviceクラスの実装の影響を受けずにControllerクラスをテストすることができます。

サンプルコード

テスト対象クラス

今回のサンプルにおけるテスト対象クラスは以下のCalculatorクラスです。
ユーザー登録を行うメソッド(createUser)をテスト対象とします。

 /**
 * ユーザー情報を扱うController.
 */
@RestController
@AllArgsConstructor
public class UserController implements UsersApi {

    private final CreateUserService createUserService;
    private final LoginService loginService;

    /**
     * 新規ユーザーを作成する.
     *
     * @param request リクエストボディ
     * @return レスポンス
     */
    @Override
    public ResponseEntity<CreatedResponse> createUser(CreateUserRequest request) {
        try {
            createUserService.createUser(new UserFormParam(request));
        } catch (CheckedException e) {
            if (ResponseCode.BAD_REQUEST == e.getCode()) {
                throw new BadRequestException(e.getMessage());
            }
        }
        return ResponseEntity.status(HttpStatus.CREATED)
                .body(new CreatedResponse()
                        .code("Created")
                        .message("ユーザーを作成しました。")
                );
    }
}

テストクラス

テスト対象クラスに対するテストクラスは以下のようになります。
赤字箇所の重要ポイントについて以下で説明します。

//(1)
@SpringBootTest
class UserControllerTest {
//(2)
    @InjectMocks
    private UserController userController;

    @Mock
    private CreateUserService createUserService;

    private MockMvc mockMvc;

    private AutoCloseable closeable;

    @BeforeEach
    void setUp() {
        closeable = MockitoAnnotations.openMocks(userController);
        mockMvc = MockMvcBuilders.standaloneSetup(userController) .build();
    }

    @AfterEach
    void closeMocks() throws Exception {
        closeable.close();
    }

    @Test
    @DisplayName("createUser_正常系")
    void createUser() throws Exception {

        // データ準備
        // モックの動作を指定(3)
        Mockito.when(createUserService.createUser(any())).thenReturn(new CreateUserResult(1L));

        String requestBody = "{\n" +
                "  \"firstName\": \"田中\",\n" +
                "  \"lastName\": \"太郎\",\n" +
                "  \"age\": \"12\",\n" +
                "  \"tel\": \"080-8726-2211\",\n" +
                "  \"address\": \"じゅうしょ\",\n" +
                "  \"email\": \"test@gmail.com\",\n" +
                "  \"password\": \"password\"\n" +
                "}";
        String expected = "{\"code\":\"Created\",\"message\":\"ユーザーを作成しました。\"}";

        // 実行&結果検証(4)
        MockHttpServletRequestBuilder request =
                MockMvcRequestBuilders.post("/users")
                        .content(requestBody)
                        .contentType(MediaType.APPLICATION_JSON_VALUE);
        String actual = mockMvc.perform(request)
                .andExpect(MockMvcResultMatchers.status().isCreated())
                .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON_VALUE))
                .andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8);

        Mockito.verify(createUserService, Mockito.times(1)).createUser(any());

        assertEquals(expected, actual);
    }
    
}

(1)@SpringBootTestの付与

@SpringBootTestはテスト環境でもSpring Frameworkの機能を使用するためのアノテーションです。
通常の@TestではDIコンテナが動作せず、自動でインスタンス化されないため本アノテーションをつける必要があります。

公式はこちら

(2)componentのモック化

@InjectMocksと@Mock

@InjectMocks:
テスト対象クラス(controller)に付与する。テスト対象クラスに生成したモックを注入します。
@Mock:
モック化するクラス(service)に付与する。このアノテーションを付与したクラスがモック化されます。

    @InjectMocks
    private UserController userController;

    @Mock
    private CreateUserService createUserService;
モックのセットアップ

MockMvcBuilders.standaloneSetup()の引数にテスト対象のControllerをセットします。

   @BeforeEach
    void setUp() {
        closeable = MockitoAnnotations.openMocks(userController);
        mockMvc = MockMvcBuilders.standaloneSetup(userController).build();
    }

(3)モックの動作指定

モックの動作を指定しています。when()にControllerから呼ばれるSeriviceクラスのメソッド、thenReturn()に呼ばれた際の戻り値を指定します。

        Mockito.when(createUserService.createUser(any())).thenReturn(new CreateUserResult(1L));

(4)実行&結果検証

以下でAPIをCallするための準備をしています。MockMvcRequestBuilders.post(“/users”)でHTTPメソッドの種類、URIを指定しています。getの場合はMockMvcRequestBuilders.get(“/users”)ですね。
contentにrequestBodyの情報を詰めています。

        MockHttpServletRequestBuilder request =
                MockMvcRequestBuilders.post("/users")
                        .content(requestBody)
                        .contentType(MediaType.APPLICATION_JSON_VALUE);

以下でAPIのCall、結果の検証を行っています。
mockMvc.perform()に上記で作成したリクエスト入れることでリクエストしています。

.andExpected()ではレスポンスのステータスコード、ContentTypeを検証しています。
Mockito.verifyでは、モックの呼ばれる回数を検証しています。今回の場合、Serviceのメソッドが想定通り、1回呼ばれることを検証しています。

assertEquals()では、responseBodyの検証をしています。

String actual = mockMvc.perform(request)
                .andExpect(MockMvcResultMatchers.status().isCreated())
                .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON_VALUE))
                .andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8);

        Mockito.verify(createUserService, Mockito.times(1)).createUser(any());

        assertEquals(expected, actual);

終わりに

本記事はここまでとなります。
ご覧いただきありがとうございました。ご指摘等がございましたら頂けますと嬉しいです。
引き続き、プログラミングについて定期的に発信していきますのでよろしくお願いします!
また、もしよろしければtwitterもフォローしていただけると嬉しいです!🐢

コメント