引数をもつメソッドの戻り値や例外を設定する方法は、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()で指定)を指定すればよい。






