JUnitでResponseEntityExceptionHandlerを有効にする

【前提条件】

[環境]
  • JDK 1.8.66
  • Spring Boot 1.3.3

【概要】

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を返したいと思った時につまづいたので書いてみました。