引数をもつメソッドの戻り値や例外を設定する方法は、MockitoとPowerMockそれぞれで異なっていることがわかったので、今回は防備録として、そのサンプルプログラムを共有する。
前提条件
下記記事の「起動ポートの変更」までの手順が完了していること。
やってみたこと
テスト対象プログラムの作成
作成したサンプルプログラムの構成は以下の通り。
なお、上図の赤枠は、今回記載するサンプルプログラムの内容である。
テスト対象のコントローラクラスの内容は以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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"; } } |
また、上記プログラムから呼び出されるコンポーネントクラス・ユーティリティクラスの内容は以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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は以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <!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> |
1 2 3 4 5 6 7 8 9 10 11 12 | <!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が利用できるための設定を追加している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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のサンプルプログラムの内容は以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | 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()で指定)を指定すればよい。