JUnitでResponseEntityExceptionHandlerを有効にする
【概要】
Spring BootでResponseEntityExceptionHandlerをJUnitでも動かす方法です。
通常であれば例外ハンドラを意識せずにテスコードは書けます。
しかし、MockMvcBuilders#standaloneSetupを使う場合は自動で例外ハンドラが設定されないため、
テストコードのセットアップ時に少し設定が必要になります。
【サンプルコード】
サンプルコードはこちらにあります。
ブログのエントリ上ではかなり省略しているので、詳細が気になる方はサンプルコードをみてください。
【コントローラと例外ハンドラ】
対象となるコントローラと例外ハンドラのコードは下記のような感じです。
[コントローラ]
package jp.glory.sample.web; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import jp.glory.sample.exceptioin.OriginalException; import jp.glory.sample.nonweb.CodeChecker; import jp.glory.sample.web.response.SampleResonse; @RestController @RequestMapping("sample/{id}") public class SampleApi { private final CodeChecker checker; @Autowired public SampleApi(final CodeChecker checker) { this.checker = checker; } @RequestMapping public ResponseEntity<SampleResonse> checkParameter(@PathVariable int id) { if (!checker.isValid(id)) { throw new OriginalException(id); } SampleResonse response = new SampleResonse(); response.setNewId(id * 100); return new ResponseEntity<SampleResonse>(response, HttpStatus.OK); } }
CodeCheckerクラスは何かしらの処理をするクラスで、今回はMockitoでモックを作成する対象になります。
SampleResonseクラスはステータスOKだった時に返されるJSONのBean、
OriginalExceptionは独自に作った例外でハンドラでハンドリングします。
[例外ハンドラ]
package jp.glory.sample.web.handler; import org.springframework.beans.TypeMismatchException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import jp.glory.sample.exceptioin.OriginalException; import jp.glory.sample.web.response.ErrorResponse; @ControllerAdvice public class WebExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(OriginalException.class) public ResponseEntity<ErrorResponse> handleOriginalException(OriginalException ex) { ErrorResponse response = new ErrorResponse(); response.setMessage("Throw OriginalException!!"); return new ResponseEntity<ErrorResponse>(response, HttpStatus.BAD_REQUEST); } @Override protected ResponseEntity<Object> handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { ErrorResponse response = new ErrorResponse(); response.setMessage("Throw TypeMismatchException!!"); return new ResponseEntity<Object>(response, HttpStatus.BAD_REQUEST); } }
ResponseEntityExceptionHandlerクラスを継承した、独自の例外ハンドラです。
独自のOriginalExceptionと
Spring Framework側で提供されているTypeMismatchExceptionをハンドリングしています。
【テストコード】
テストコードについては長くなってしまうのでセットアップだけ見ていきます。
package jp.glory.sample.web; // importは省略 @RunWith(Enclosed.class) public class SampleApiTest { @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(ApplicationRoot.class) @WebAppConfiguration public static class Mockitoしない場合 { @Autowired private WebApplicationContext wac; private MockMvc mockMvc; @Before public void setUp() { this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); } // テストコードは省略 // @Test ... } @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(ApplicationRoot.class) @WebAppConfiguration public static class Mockitoする場合 { @Rule public final MockitoRule rule = MockitoJUnit.rule(); private MockMvc mockMvc; @InjectMocks private SampleApi sut = null; // モックのインジェクション先 @Autowired private HandlerExceptionResolver handlerExceptionResolver; @Mock private CodeChecker mockChecker = null; // モックオブジェクト @Before public void setUp() { this.mockMvc = MockMvcBuilders.standaloneSetup(sut) .setHandlerExceptionResolvers(handlerExceptionResolver).build(); } // テストコードは省略 // @Test ... } }
通常はMockMvcBuilders.webAppContextSetupメソッドを使用すれば、
例外が発生した時に先ほど設定した例外ハンドラが自動でセットアップされます。
そのため、webAppContextSetupのパターンは特別な設定は不要です。
MockMvcBuilders.standaloneSetupメソッドを使用する場合は例外ハンドラが設定されません。
そのため、テストコード側でHandlerExceptionResolverというクラスをインジェクションし、
setHandlerExceptionResolversメソッドで設定する必要があります。
設定はこれだけです。
【まとめ】
Mockitoを使ったテストコードで独自例外が投げられた場合は
独自のエラー用JSONを返したいと思った時につまづいたので書いてみました。