引数をもつメソッドの戻り値や例外を設定する方法は、MockitoとPowerMockそれぞれで異なっていることがわかったので、今回は防備録として、そのサンプルプログラムを共有する。
前提条件
下記記事の「起動ポートの変更」までの手順が完了していること。
やってみたこと
テスト対象プログラムの作成
作成したサンプルプログラムの構成は以下の通り。
なお、上図の赤枠は、今回記載するサンプルプログラムの内容である。
テスト対象のコントローラクラスの内容は以下の通り。
package com.example.demo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class DemoController { @Autowired private DemoComponent demoComponent; @RequestMapping("/") public String index(Model model){ String textString1; String textString2; try{ //2つのファイルからそれぞれ値を取得 textString1 = DemoUtil.getTextString("C:\\tmp\\test.txt"); textString2 = demoComponent.getTextString("C:\\tmp\\test2.txt"); }catch (Exception e){ String errMsg = "入出力例外が発生しました"; //エラー時はerror.htmlに遷移 model.addAttribute("errMsg", errMsg); return "error"; } //正常時はindex.htmlに遷移 model.addAttribute("textString1", textString1); model.addAttribute("textString2", textString2); return "index"; } }
また、上記プログラムから呼び出されるコンポーネントクラス・ユーティリティクラスの内容は以下の通り。
package com.example.demo; import org.springframework.stereotype.Component; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; @Component public class DemoComponent { public String getTextString(String filePath) throws IOException{ StringBuilder sb = new StringBuilder(); try(BufferedReader br = new BufferedReader( new FileReader(filePath))) { String line; while ((line = br.readLine()) != null) { sb.append(line); } }catch (IOException ex){ throw ex; } return sb.toString(); } }
package com.example.demo; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class DemoUtil { public static String getTextString(String filePath) throws IOException{ StringBuilder sb = new StringBuilder(); try(BufferedReader br = new BufferedReader(new FileReader(filePath))) { String line; while ((line = br.readLine()) != null) { sb.append(line); } }catch (IOException ex){ throw ex; } return sb.toString(); } }
さらに、正常時に遷移するindex.html、エラー時に遷移するerror.htmlは以下の通り。
<!DOCTYPE html> <html lang="ja" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>index page</title> </head> <body> <p th:text="${textString1}"> ここにtextString1の値が設定されます </p> <p th:text="${textString2}"> ここにtextString2の値が設定されます </p> <br/><br/> </body> </html>
<!DOCTYPE html> <html lang="ja" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>error page</title> </head> <body> <p th:text="${errMsg}"> ここに発生したエラーメッセージが設定されます </p> </body> </html>
テスト対象プログラムの実行
テスト対象プログラムを実行する前に、まずは以下のファイル「test.txt」「test2.txt」をC:\tmp下に配置する。
上記状態で、Spring Bootアプリケーションを起動し、「http:// (ホスト名):(ポート番号)」とアクセスすると、以下の画面(index.html)が表示される。
C:\tmp 下に「test.txt」または「test2.txt」が無い状態で、Spring Bootアプリケーションを起動し、「http:// (ホスト名):(ポート番号)」とアクセスすると、以下のエラー画面(error.html)が表示される。
JUnitのプログラムの作成と実行
build.gradleの内容は以下の通り。PowerMockが利用できるための設定を追加している。
plugins { id 'org.springframework.boot' version '2.1.7.RELEASE' id 'java' } apply plugin: 'io.spring.dependency-management' group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '1.8' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' //PowerMockが利用できるための設定 testCompile 'org.powermock:powermock-module-junit4:2.0.0-RC.4' testCompile 'org.powermock:powermock-api-mockito2:2.0.0-RC.4' }
テスト対象のコントローラクラス「DemoController.java」から呼ばれるコンポーネントクラス、またはユーティリティクラスで例外を発生させた場合の、JUnitのサンプルプログラムの内容は以下の通り。
package com.example.demo; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.springframework.ui.Model; import java.io.IOException; import java.util.Map; //staticメソッドをMock化するにはPowerMockを利用 //@PrepareForTestアノテーションで、staticメソッドを含むクラスを指定 @RunWith(PowerMockRunner.class) @PrepareForTest({DemoUtil.class}) public class DemoControllerTest { /** * テスト対象クラス */ @InjectMocks private DemoController demoController; @Mock private DemoComponent demoComponent; /** * 前処理(各テストケースを実行する前に行われる処理) */ @Before public void init() { //@Mockアノテーションのモックオブジェクトを初期化 //これを実行しないと@Mockアノテーション、@InjectMocksを付与した //Mockオブジェクトが利用できない MockitoAnnotations.initMocks(this); //DemoUtilクラスをMock化 PowerMockito.mockStatic(DemoUtil.class); } /** * DemoControllerクラスのindexメソッドの確認 */ @Test public void testDemoControllerNormal() throws Exception { //Mockオブジェクト呼出時の戻り値を設定(DemoUtil.java) //when句に(staticメソッドをもつ)クラス名・staticメソッド名・引数を順に指定するが //例外が発生し得るため、「throws Exception」を付与する PowerMockito.doReturn("test1") .when(DemoUtil.class, "getTextString", Mockito.any()); //Mockオブジェクト呼出時の戻り値を設定(DemoComponent.java) Mockito.doReturn("test2") .when(demoComponent).getTextString(Mockito.any()); //modelオブジェクトを取得し、テスト対象クラスのメソッドを実行 Model model = DemoControllerTestUtil.getModel(); String returnVal = demoController.index(model); //戻り値が"index"であることを確認 assertEquals("index", returnVal); //modelオブジェクトの設定値を確認 //demoComponent.getTextStringが呼ばれた場合は設定したMockの戻り値が設定され、 //エラーメッセージが設定されないことを確認 Map<String, Object> modelValue = model.asMap(); assertEquals("test1", modelValue.get("textString1")); assertEquals("test2", modelValue.get("textString2")); assertNull(modelValue.get("errMsg")); } @Test public void testDemoControllerDemoUtilException() throws Exception { //Mockオブジェクト呼出時の例外を設定(DemoUtil.java) //doThrow句内に例外を設定するが、発生し得ない例外(例:Exception)は設定できない PowerMockito.doThrow(new IOException()) .when(DemoUtil.class, "getTextString", Mockito.any()); //Mockオブジェクト呼出時の戻り値を設定(DemoComponent.java) Mockito.doReturn("test2") .when(demoComponent).getTextString(Mockito.any()); //modelオブジェクトを取得し、テスト対象クラスのメソッドを実行 Model model = DemoControllerTestUtil.getModel(); String returnVal = demoController.index(model); //戻り値が"error"であることを確認 assertEquals("error", returnVal); //modelオブジェクトの設定値を確認 //IOExceptionが発生した場合のエラーメッセージ「入出力例外が発生しました」が //設定され、textString1・textString2が設定されないことを確認 Map<String, Object> modelValue = model.asMap(); assertEquals("入出力例外が発生しました", modelValue.get("errMsg")); assertNull(modelValue.get("textString1")); assertNull(modelValue.get("textString2")); } @Test public void testDemoControllerDemoComponentException() throws Exception { //Mockオブジェクト呼出時の戻り値を設定(DemoUtil.java) PowerMockito.doReturn("test1") .when(DemoUtil.class, "getTextString", Mockito.any()); //Mockオブジェクト呼出時の例外を設定(DemoComponent.java) Mockito.doThrow(new IOException()) .when(demoComponent).getTextString(Mockito.any()); //modelオブジェクトを取得し、テスト対象クラスのメソッドを実行 Model model = DemoControllerTestUtil.getModel(); String returnVal = demoController.index(model); //戻り値が"error"であることを確認 assertEquals("error", returnVal); //modelオブジェクトの設定値を確認 //IOExceptionが発生した場合のエラーメッセージ「入出力例外が発生しました」が //設定され、textString1・textString2が設定されないことを確認 Map<String, Object> modelValue = model.asMap(); assertEquals("入出力例外が発生しました", modelValue.get("errMsg")); assertNull(modelValue.get("textString1")); assertNull(modelValue.get("textString2")); } }
上記サンプルプログラムのように、PowerMockitoでwhen句を利用する場合は、第一引数にstaticメソッドを含むクラス名.classを、第二引数にメソッド名を、第三引数以降にメソッドの引数(任意値はMockito.any()で指定)を指定することで、引数を含むメソッドの戻り値や例外を設定できる。
また、Mockitoでwhen句を利用する場合は、when句の後に指定するメソッドに引数(任意値はMockito.any()で指定)を指定すればよい。
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/junit-exception-has-args/demo
要点まとめ
- PowerMockitoでwhen句を利用する場合は、第一引数にstaticメソッドを含むクラス名.classを、第二引数にメソッド名を、第三引数以降にメソッドの引数(任意値はMockito.any()で指定)を指定すれば、引数を含むメソッドの戻り値や例外を設定できる。
- Mockitoでwhen句を利用する場合は、when句の後に指定するメソッドに引数(任意値はMockito.any()で指定)を指定すればよい。