WEB

[Java, SpringBoot, JUnit] API呼び出しの処理をテスト時はスタブに置き換える

WEB
この記事は約8分で読めます。

はじめに

業務で以下のような対応をしたので記事にしてみました。

対象読者

「〇〇APIをコールする処理を作って!〇〇APIはまだ出来てないから、テストはスタブを作って対応して!」
と言われた経験浅めのJavaプレイヤー

概要

スタブを作成して、DIに注入することで「API呼び出しの処理をテスト時はスタブに置き換える」を達成します。
匿名クラス・ラムダ式の使い方とかも簡単にですが、説明します。

環境

OS: macOS Catalina 10.15.7
IDE: Pleiades All in One Eclipse 2021
Java: 8
SpringBoot: 2.5.0
JUnit: 5

注意事項

本記事執筆者は執筆時点でJava歴・プログラマ歴共に約1年のペーペーです。
もっといい書き方とかいっぱいあると思うので、全て鵜呑みにしてはいけません。
※間違いなどあればご指摘いただけると幸いです。

省略していること

  1. JUnitの導入方法、書き方
  2. APIをコールする具体的な実装
  3. SpringBootに関する基礎的な事項について

作成するファイル

フォルダ階層

本件説明用にプロジェクトを作りました。
基本的にはどのようなディレクトリでも動作しますので、お好きな構成にしてみてください。


コントローラ

TestController.java

package com.example.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.service.TestService;

@RestController
public class TestController {

    @Autowired
    TestService testService;

    @RequestMapping(value = "/test", method = RequestMethod.GET)
    public ResponseEntity<?> test(){
        return new ResponseEntity<>(testService.doProcess(), HttpStatus.OK);
    }
}

ただのコントローラです。
/test へのGETリクエストに対して、サービスクラスから取得した文字列を返しています。

コントローラから呼び出されるサービスクラス

TestService.java

package com.example.demo.service;

public interface TestService {
    public String doProcess();
}

TestServiceImpl.java

package com.example.demo.service;

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

@Service
public class TestServiceImpl implements TestService {

    @Autowired
    CallAPIService callAPIService;

    @Override
    public String doProcess() {
        return callAPIService.callAPI();
    }
}

コントローラから呼び出されるサービスクラスです。

実際にAPIをコールするクラスのインスタンスをDIして呼び出した結果を返却しています。

APIをコールするサービスクラス(処理は割愛)

CallAPIService.java

package com.example.demo.service;

public interface CallAPIService {
    public String callAPI();
}

CallAPIServiceImpl.java

package com.example.demo.service;

import org.springframework.stereotype.Service;

@Service
public class CallAPIServiceImpl implements CallAPIService {
    /*
     * APIをコールします(処理は割愛)
     */
    @Override
    public String callAPI() {
        return "callAPIメソッドを実行しました。";
    }
}

具体的なAPIコール処理などを割愛していますが、
APIを叩いた結果が文字列の

“callAPIメソッドを実行しました。”

だと置き換えて考えてください!

テストクラス(JUnit)

TestServiceImplTest.java

package com.example.demo.service;

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

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class TestServiceImplTest {

    @Autowired
    TestServiceImpl service;

    @Test
    public void test() {
        // 期待値を設定
        String expected = "callAPIメソッドのスタブを実行しました";
        // 期待値を返却するだけのスタブを作成
        CallAPIService stub = () -> expected;
        // スタブをテスト対象サービスのDIに注入
        service.callAPIService = stub;

        assertEquals(expected, service.doProcess());
    }
}

解説します。

// 期待値を返却するだけのスタブを作成
CallAPIService stub = () -> expected;

上記の部分で簡易的にですが、CallAPIService型のスタブを作成しています。

※業務レベルではもっとスタブに機能を持たせる必要がありますが、今回は割愛。
※ラムダ式で実装できるのは、実装したいインターフェースがSAM(単一の抽象メソッドを保持するインターフェース)の場合です。

実装したいインターフェースがSAMでない場合や、ラムダ式によるインターフェースの実装が得意でない場合は、以下のような書き方をしましょう。

CallAPIService stub = new CallAPIService() {
    @Override
    public String callAPI() {
        return "callAPIメソッドのスタブを実行しました";
    }
};

どちらもやっていることは一緒です。

具体的には、CallAPIServiceインターフェースを実装したクラスのインスタンスを生成している。

ということです。

続いて以下の処理でサービスのDIに上記処理で作成したスタブのインスタンスを注入します。

service.callAPIService = stub;

これで、TestServiceImplが呼び出すCallAPIServiceのインスタンスは、スタブに書き換えられました。

※もし、注入対象のフィールドがprivateなフィールドだった場合、本記事では紹介しませんが、リフレクションを使うなどすれば対応できるかと思います・・・。

最後に以下のアサーションでスタブが呼び出された場合のみテストが成功する。
という条件に設定しました。

assertEquals(expected, service.doProcess());

これで、準備は完了です。

まずは、普通に作成したAPIを叩いてみる

SpringBootを実行する

$ mvn spring-boot:run

APIをコールする

$ curl http://localhost:8080/test

結果

callAPIメソッドを実行しました。

作成した通りの結果となりました。

続いてテストを実行する

テストは、スタブが呼び出されなければ成功しない仕様でしたね。
ご自身のIDEでJUnitを実行してください。

つまり、「API呼び出しの処理をテスト時はスタブに置き換える」ということが出来ました。

あとがき

いろいろ省略してしまいましたが、
上記のスタブに機能追加することで、より実践的なスタブが作成できるようになるかと思います。

この記事が少しでも参考になれば幸いです!