これまでは、Mockitoを利用してインスタンスメソッドをMock化する方法について記載していたが、今回はstaticメソッドのMock化と呼出確認をしてみたので、そのサンプルプログラムを共有する。
staticメソッドのMock化は、Mockitoでは実行できないので、PowerMockというライブラリを利用する。
前提条件
下記記事の実装が完了していること。
サンプルプログラムの内容
作成したサンプルプログラムの構成は以下の通り。

なお、上記の赤枠のうち、「build.gradle」が変更したプログラムで、他は今回新規で作成したプログラムとなる。
build.gradleの内容は以下の通り。テストクラスでlombokを利用できる設定・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'
compileOnly 'org.projectlombok:lombok:1.18.10'
annotationProcessor 'org.projectlombok:lombok:1.18.10'
compile files('lib/ojdbc6.jar')
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.1'
//lombokがテストクラスで利用できるようにするための設定
testCompileOnly 'org.projectlombok:lombok:1.18.10'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.10'
//PowerMockが利用できるための設定
testCompile 'org.powermock:powermock-module-junit4:2.0.0-RC.4'
testCompile 'org.powermock:powermock-api-mockito2:2.0.0-RC.4'
}
さらに、「DemoServiceImplTest3.java」の内容は以下の通り。
package com.example.demo;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import java.time.LocalDate;
import java.util.Map;
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.validation.BindingResult;
import static org.junit.Assert.assertEquals;
//staticメソッドをMock化するにはPowerMockを利用
//@PrepareForTestアノテーションで、staticメソッドを含むクラスを指定
@RunWith(PowerMockRunner.class)
@PrepareForTest({DateCheckUtil.class})
public class DemoServiceImplTest3 {
/**
* テスト対象のクラス
* (今回はSpring Bootを利用しないため、Serviceではなく
* ServiceImplを対象クラスに指定している)
*/
@InjectMocks
private DemoServiceImpl demoServiceImpl;
private DemoForm demoForm;
private BindingResult bindingResult;
/**
* 前処理(各テストケースを実行する前に行われる処理)
*/
@Before
public void init() {
//@Mockアノテーションのモックオブジェクトを初期化
//これを実行しないと@Mockアノテーション、@InjectMocksを付与した
//Mockオブジェクトが利用できない
MockitoAnnotations.initMocks(this);
//DateCheckUtilクラスをMock化
PowerMockito.mockStatic(DateCheckUtil.class);
//テスト対象メソッドの引数を設定
demoForm = makeDemoForm(Long.valueOf(1), "テスト プリン"
, LocalDate.of(2012, 1, 15), SexEnum.MAN);
bindingResult = BindingResultModel.getBindingResult();
}
/**
* DemoServiceImplクラスのcheckFormメソッド(DateCheckUtil.checkDateが正常時)の確認
*/
@Test
public void testCheckFormNormal() {
//DateCheckUtilメソッドをMock化し、0が返却されるように設定
PowerMockito.when(DateCheckUtil.checkDate(
Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(0);
//テスト対象メソッドの実行
String returnVal = demoServiceImpl.checkForm(demoForm, bindingResult);
//テスト対象メソッドを実行した結果、戻り値がconfirmであることを確認
assertEquals("confirm", returnVal);
//テスト対象メソッドを実行した結果、bindingResultに
//エラーメッセージが設定されないことを確認
Map<String, Object> mapObj = bindingResult.getModel();
assertEquals(0, mapObj.size());
}
/**
* DemoServiceImplクラスのcheckFormメソッド(DateCheckUtil.checkDateが異常時)の確認
*/
@Test
public void testCheckFormalDateError() {
//DateCheckUtilメソッドをMock化し、4が返却されるように設定
PowerMockito.when(DateCheckUtil.checkDate(
Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(4);
//テスト対象メソッドの実行
String returnVal = demoServiceImpl.checkForm(demoForm, bindingResult);
//テスト対象メソッドを実行した結果、戻り値がinputであることを確認
assertEquals("input", returnVal);
//テスト対象メソッドを実行した結果、resultに生年月日の日付が
//不正な場合のエラーメッセージが設定されることを確認
Map<String, Object> mapObj = bindingResult.getModel();
BindingResultRejectValueModel resultModelYear
= (BindingResultRejectValueModel) mapObj.get("birthYear");
assertEquals("validation.date-invalidate", resultModelYear.getErrorCode());
BindingResultRejectValueModel resultModelMonth
= (BindingResultRejectValueModel) mapObj.get("birthMonth");
assertEquals("validation.empty-msg", resultModelMonth.getErrorCode());
BindingResultRejectValueModel resultModelDay
= (BindingResultRejectValueModel) mapObj.get("birthDay");
assertEquals("validation.empty-msg", resultModelDay.getErrorCode());
}
@Test
public void testCheckFormDateCheckUtilArgs() {
//DateCheckUtilメソッドをMock化し、0が返却されるように設定
PowerMockito.when(DateCheckUtil.checkDate(
Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(0);
//テスト対象メソッドの実行
String returnVal = demoServiceImpl.checkForm(demoForm, bindingResult);
//DateCheckUtilが呼ばれたときの引数を取得するための設定
ArgumentCaptor<String> strYearArg = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> strMonthArg = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> strDayArg = ArgumentCaptor.forClass(String.class);
//DateCheckUtilが1回呼ばれたことを確認
PowerMockito.verifyStatic(DateCheckUtil.class, Mockito.times(1));
DateCheckUtil.checkDate(
strYearArg.capture(), strMonthArg.capture(), strDayArg.capture());
//DateCheckUtilの引数が2012年1月15日の年月日であることを確認
assertEquals("2012", strYearArg.getValue());
assertEquals("1", strMonthArg.getValue());
assertEquals("15", strDayArg.getValue());
//テスト対象メソッドを実行した結果、戻り値がconfirmであることを確認
assertEquals("confirm", returnVal);
//テスト対象メソッドを実行した結果、bindingResultに
//エラーメッセージが設定されないことを確認
Map<String, Object> mapObj = bindingResult.getModel();
assertEquals(0, mapObj.size());
}
/**
* Demoフォームオブジェクトを生成する
*
* @param id ID
* @param name 名前
* @param birthDay 生年月日
* @param sexEnum 性別Enum
* @return Demoフォームオブジェクト
*/
private DemoForm makeDemoForm(Long id, String name, LocalDate birthDay
, SexEnum sexEnum) {
DemoForm demoForm = new DemoForm();
if (id != null) {
demoForm.setId(String.valueOf(id));
}
demoForm.setName(name);
if (birthDay != null) {
demoForm.setBirthYear(String.valueOf(birthDay.getYear()));
demoForm.setBirthMonth(String.valueOf(birthDay.getMonthValue()));
demoForm.setBirthDay(String.valueOf(birthDay.getDayOfMonth()));
}
if (sexEnum != null) {
demoForm.setSex(sexEnum.getSex());
demoForm.setSex_value(sexEnum.getSex_value());
}
return demoForm;
}
}
PowerMockを利用できるために、クラスの先頭に「@RunWith(PowerMockRunner.class)」「@PrepareForTest({(staticメソッドをもつクラス).class})」というアノテーションを追加している。また、initメソッド内で「PowerMockito.mockStatic((staticメソッドをもつクラス).class)」を実行することで、指定したstaticメソッドがMock化している。
さらに、各テストメソッド内で「PowerMockito.when(staticメソッド(引数)).thenReturn(戻り値)」を実行することで、指定したstaticメソッドの戻り値を指定している。
また、「testCheckFormDateCheckUtilArgs」メソッド内で、staticメソッドの呼出確認とそのときの引数を取得している。テスト対象クラス実行後に、「PowerMockito.verifyStatic(staticメソッドをもつクラス.class, Mockito.times(呼出回数))」を実行し、その直後に呼出確認対象となるstaticメソッドを実行することで、staticメソッドの呼出回数確認が行える。さらに、Mockito利用時と同様に、そのメソッド呼出時の引数は、ArgumentCaptorクラスを利用することで取得できる。
その他、「BindingResultModel.java」「BindingResultRejectValueModel.java」の内容は以下の通り。
package com.example.demo;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import java.beans.PropertyEditor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BindingResultModel {
public static BindingResult getBindingResult() {
BindingResult result = new BindingResult() {
Map<String, Object> modelMap = new HashMap<>();
@Override
public Object getTarget() {
return null;
}
@Override
public Map<String, Object> getModel() {
return modelMap;
}
@Override
public Object getRawFieldValue(String field) {
return null;
}
@Override
public PropertyEditor findEditor(String field, Class<?> valueType) {
return null;
}
@Override
public PropertyEditorRegistry getPropertyEditorRegistry() {
return null;
}
@Override
public String[] resolveMessageCodes(String errorCode) {
return new String[0];
}
@Override
public String[] resolveMessageCodes(String errorCode, String field) {
return new String[0];
}
@Override
public void addError(ObjectError error) {
}
@Override
public String getObjectName() {
return null;
}
@Override
public void setNestedPath(String nestedPath) {
}
@Override
public String getNestedPath() {
return null;
}
@Override
public void pushNestedPath(String subPath) {
}
@Override
public void popNestedPath() throws IllegalStateException {
}
@Override
public void reject(String errorCode) {
}
@Override
public void reject(String errorCode, String defaultMessage) {
}
@Override
public void reject(String errorCode, Object[] errorArgs
, String defaultMessage) {
}
@Override
public void rejectValue(String field, String errorCode) {
BindingResultRejectValueModel obj = new BindingResultRejectValueModel();
obj.setErrorCode(errorCode);
modelMap.put(field, obj);
}
@Override
public void rejectValue(String field, String errorCode
, String defaultMessage) {
BindingResultRejectValueModel obj = new BindingResultRejectValueModel();
obj.setErrorCode(errorCode);
obj.setDefaultMessage(defaultMessage);
modelMap.put(field, obj);
}
@Override
public void rejectValue(String field, String errorCode
, Object[] errorArgs, String defaultMessage) {
BindingResultRejectValueModel obj = new BindingResultRejectValueModel();
obj.setErrorCode(errorCode);
obj.setErrorArgs(errorArgs);
obj.setDefaultMessage(defaultMessage);
modelMap.put(field, obj);
}
@Override
public void addAllErrors(Errors errors) {
}
@Override
public boolean hasErrors() {
return false;
}
@Override
public int getErrorCount() {
return 0;
}
@Override
public List<ObjectError> getAllErrors() {
return null;
}
@Override
public boolean hasGlobalErrors() {
return false;
}
@Override
public int getGlobalErrorCount() {
return 0;
}
@Override
public List<ObjectError> getGlobalErrors() {
return null;
}
@Override
public ObjectError getGlobalError() {
return null;
}
@Override
public boolean hasFieldErrors() {
return false;
}
@Override
public int getFieldErrorCount() {
return 0;
}
@Override
public List<FieldError> getFieldErrors() {
return null;
}
@Override
public FieldError getFieldError() {
return null;
}
@Override
public boolean hasFieldErrors(String field) {
return false;
}
@Override
public int getFieldErrorCount(String field) {
return 0;
}
@Override
public List<FieldError> getFieldErrors(String field) {
return null;
}
@Override
public FieldError getFieldError(String field) {
return null;
}
@Override
public Object getFieldValue(String field) {
return null;
}
@Override
public Class<?> getFieldType(String field) {
return null;
}
};
return result;
}
}
package com.example.demo;
import lombok.Data;
@Data
public class BindingResultRejectValueModel {
private String errorCode;
private Object[] errorArgs;
private String defaultMessage;
}
なお、「BindingResultModel.java」BindingResultオブジェクトの生成のために、「BindingResultRejectValueModel.java」はBindingResultオブジェクトの設定値を確認するために、それぞれ追加している。
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/junit-powermock-static/demo
今回作成した「DemoServiceImplTest3.java」の実行結果は以下の通り。ワーニングメッセージが出力されるが、テストメソッドが正常に実行できたことが確認できる。

要点まとめ
- PowerMockというライブラリを利用すると、staticメソッドをMock化することができる。
- staticメソッドをMock化するには、クラスの先頭に「@RunWith(PowerMockRunner.class)」「@PrepareForTest({(staticメソッドをもつクラス).class})」というアノテーションを付与し、「PowerMockito.mockStatic((staticメソッドをもつクラス).class)」を実行すればよい。
- 「PowerMockito.when(staticメソッド(引数)).thenReturn(戻り値)」を実行することで、指定したstaticメソッドの戻り値を指定することができる。
- テスト対象クラス実行後に、「PowerMockito.verifyStatic(staticメソッドをもつクラス.class, Mockito.times(呼出回数))」を実行し、その直後に呼出確認対象となるstaticメソッドを実行することで、staticメソッドの呼出回数確認が行える。また、そのメソッド呼出時の引数は、ArgumentCaptorクラスを利用することで取得できる。





