これまでは、Mockitoを利用してインスタンスメソッドをMock化する方法について記載していたが、今回はstaticメソッドのMock化と呼出確認をしてみたので、そのサンプルプログラムを共有する。
staticメソッドのMock化は、Mockitoでは実行できないので、PowerMockというライブラリを利用する。
前提条件
下記記事の実装が完了していること。
サンプルプログラムの内容
作成したサンプルプログラムの構成は以下の通り。
なお、上記の赤枠のうち、「build.gradle」が変更したプログラムで、他は今回新規で作成したプログラムとなる。
build.gradleの内容は以下の通り。テストクラスでlombokを利用できる設定・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 | 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」の内容は以下の通り。
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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | 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」の内容は以下の通り。
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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | 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; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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クラスを利用することで取得できる。