はじめに
業務で以下のような対応をしたので記事にしてみました。
対象読者
「〇〇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年のペーペーです。
もっといい書き方とかいっぱいあると思うので、全て鵜呑みにしてはいけません。
※間違いなどあればご指摘いただけると幸いです。
省略していること
- JUnitの導入方法、書き方
- APIをコールする具体的な実装
- 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呼び出しの処理をテスト時はスタブに置き換える」ということが出来ました。
あとがき
いろいろ省略してしまいましたが、
上記のスタブに機能追加することで、より実践的なスタブが作成できるようになるかと思います。
この記事が少しでも参考になれば幸いです!