1. 序章

このチュートリアルでは、SpringのMockMvc上に構築されたREST保証APIであるRestAssuredMockMvcを使用してSpringRESTコントローラーをテストする方法を学習します。

まず、さまざまなセットアップオプションを検討します。 次に、ユニットテストと統合テストの両方を作成する方法について詳しく説明します。

このチュートリアルでは、 Spring MVC Spring MockMVC 、および REST-assured を使用しているため、これらのチュートリアルも確認してください。

2. Mavenの依存関係

テストの作成を開始する前に、 io.rest-assured:spring-mock-mvcモジュールをMavenpom.xmlにインポートする必要があります。

<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>spring-mock-mvc</artifactId>
    <version>3.3.0</version>
    <scope>test</scope>
</dependency>

3. RestAssuredMockMvcを初期化しています

次に、スタンドアロンまたは Webアプリケーションコンテキストモードのいずれかで、 RestAssuredMockMvc、DSLの開始点を初期化する必要があります。

どちらのモードでも、テストごとにジャストインタイムで実行することも、静的に1回実行することもできます。 いくつかの例を見てみましょう。

3.1. スタンドアロン

スタンドアロンモードでは、1つ以上の@Controllerまたは@ControllerAdvice注釈付きクラスを使用してRestAssuredMockMvcを初期化します。

テストが数個しかない場合は、RestAssuredMockMvcをちょうどいいタイミングで初期化できます。

@Test
public void whenGetCourse() {
    given()
      .standaloneSetup(new CourseController())
      //...
}

ただし、テストがたくさんある場合は、静的に1回だけ実行する方が簡単です。

@Before
public void initialiseRestAssuredMockMvcStandalone() {
    RestAssuredMockMvc.standaloneSetup(new CourseController());
}

3.2. Webアプリケーションのコンテキスト

Webアプリケーションコンテキストモードでは、SpringWebApplicationContextのインスタンスを使用してRestAssuredMockMvcを初期化します。

スタンドアロンモードのセットアップで見たものと同様に、RestAssuredMockMvcを各テストにちょうど間に合うように初期化できます。

@Autowired
private WebApplicationContext webApplicationContext;

@Test
public void whenGetCourse() {
    given()
      .webAppContextSetup(webApplicationContext)
      //...
}

または、繰り返しますが、静的に1回だけ実行できます。

@Autowired
private WebApplicationContext webApplicationContext;

@Before
public void initialiseRestAssuredMockMvcWebApplicationContext() {
    RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
}

4. テスト対象システム(SUT)

いくつかのテスト例に飛び込む前に、テストするものが必要になります。 @SpringBootApplication 構成から始めて、テスト対象のシステムを確認してみましょう。

@SpringBootApplication
class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

次に、Courseドメインを公開する単純な@RestControllerがあります。

@RestController
@RequestMapping(path = "/courses")
public class CourseController {

    private final CourseService courseService;

    public CourseController(CourseService courseService) {
        this.courseService = courseService;
    }

    @GetMapping(produces = APPLICATION_JSON_UTF8_VALUE)
    public Collection<Course> getCourses() {
        return courseService.getCourses();
    }

    @GetMapping(path = "/{code}", produces = APPLICATION_JSON_UTF8_VALUE)
    public Course getCourse(@PathVariable String code) {
        return courseService.getCourse(code);
    }
}
class Course {

    private String code;
    
    // usual contructors, getters and setters
}

そして、最後になりましたが、 CourseNotFoundExceptionを処理するためのサービスクラスと@ControllerAdvice

@Service
class CourseService {

    private static final Map<String, Course> COURSE_MAP = new ConcurrentHashMap<>();

    static {
        Course wizardry = new Course("Wizardry");
        COURSE_MAP.put(wizardry.getCode(), wizardry);
    }

    Collection<Course> getCourses() {
        return COURSE_MAP.values();
    }

    Course getCourse(String code) {
        return Optional.ofNullable(COURSE_MAP.get(code)).orElseThrow(() -> 
          new CourseNotFoundException(code));
    }
}
@ControllerAdvice(assignableTypes = CourseController.class)
public class CourseControllerExceptionHandler extends ResponseEntityExceptionHandler {

    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(CourseNotFoundException.class)
    public void handleCourseNotFoundException(CourseNotFoundException cnfe) {
        //...
    }
}
class CourseNotFoundException extends RuntimeException {

    CourseNotFoundException(String code) {
        super(code);
    }
}

テストするシステムができたので、いくつかのRestAssuredMockMvcテストを見てみましょう。

5. REST保証付きのRESTコントローラーユニットテスト

RestAssuredMockMvcをお気に入りのテストツールJUnitおよびMockitoと一緒に使用して、@RestControllerをテストできます。

まず、SUTをモックして構築し、次にRestAssuredMockMvcを上記のようにスタンドアロンモードで初期化します。

@RunWith(MockitoJUnitRunner.class)
public class CourseControllerUnitTest {

    @Mock
    private CourseService courseService;
    @InjectMocks
    private CourseController courseController;
    @InjectMocks
    private CourseControllerExceptionHandler courseControllerExceptionHandler;

    @Before
    public void initialiseRestAssuredMockMvcStandalone() {
        RestAssuredMockMvc.standaloneSetup(courseController, courseControllerExceptionHandler);
    }

@BeforeメソッドでRestAssuredMockMvcを静的に初期化したため、各テストで初期化する必要はありません。

スタンドアロンモードは、アプリケーションコンテキスト全体ではなく、提供するのコントローラーのみを初期化するため、単体テストに最適です。 これにより、テストが高速に保たれます。

それでは、テストの例を見てみましょう。

@Test
public void givenNoExistingCoursesWhenGetCoursesThenRespondWithStatusOkAndEmptyArray() {
    when(courseService.getCourses()).thenReturn(Collections.emptyList());

    given()
      .when()
        .get("/courses")
      .then()
        .log().ifValidationFails()
        .statusCode(OK.value())
        .contentType(JSON)
        .body(is(equalTo("[]")));
}

@RestControllerに加えて@ControllerAdviceを使用してRestAssuredMockMvcを初期化すると、例外シナリオもテストできます。

@Test
public void givenNoMatchingCoursesWhenGetCoursesThenRespondWithStatusNotFound() {
    String nonMatchingCourseCode = "nonMatchingCourseCode";

    when(courseService.getCourse(nonMatchingCourseCode)).thenThrow(
      new CourseNotFoundException(nonMatchingCourseCode));

    given()
      .when()
        .get("/courses/" + nonMatchingCourseCode)
      .then()
        .log().ifValidationFails()
        .statusCode(NOT_FOUND.value());
}

上記のように、REST-assuredは、おなじみの「then-when-then」シナリオ形式を使用してテストを定義します。

  • give() —HTTPリクエストの詳細を指定します
  • when() —HTTP動詞とルートを指定します
  • then() —HTTP応答を検証します

6. REST保証付きのRESTコントローラー統合テスト

統合テストには、RestAssuredMockMvcをSpringのテストツールと一緒に使用することもできます。

まず、 @RunWith(SpringRunner.class)および @SpringBootTest(webEnvironment = RANDOM_PORT)を使用してテストクラスを設定します。

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
public class CourseControllerIntegrationTest {
    //...
}

これにより、ランダムポートの@SpringBootApplicationクラスで構成されたアプリケーションコンテキストを使用してテストが実行されます。

次に、 WebApplicationContext を挿入し、それを使用してRestAssuredMockMvcを上記のように初期化します。

@Autowired
private WebApplicationContext webApplicationContext;

@Before
public void initialiseRestAssuredMockMvcWebApplicationContext() {
    RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
}

テストクラスが設定され、 RestAssuredMockMvc が初期化されたので、テストの作成を開始する準備が整いました。

@Test
public void givenNoMatchingCourseCodeWhenGetCourseThenRespondWithStatusNotFound() {
    String nonMatchingCourseCode = "nonMatchingCourseCode";

    given()
      .when()
        .get("/courses/" + nonMatchingCourseCode)
      .then()
        .log().ifValidationFails()
        .statusCode(NOT_FOUND.value());
}

@BeforeメソッドでRestAssuredMockMvcを静的に初期化したため、各テストで初期化する必要はありません。

RESTで保証されたAPIの詳細については、RESTで保証されたガイドをご覧ください。

7. 結論

このチュートリアルでは、REST-assuredを使用して、REST-assuredの spring-mock-mvcモジュールを使用してSpringMVCアプリケーションをテストする方法を説明しました。

RestAssuredMockMvc スタンドアロンモードで初期化すると、提供された Controller のみが初期化され、テストが高速に保たれるため、単体テストに最適です。

RestAssuredMockMvcWebアプリケーションコンテキストモードで初期化すると、完全な WebApplicationContext が使用されるため、統合テストに最適です。

いつものように、すべてのサンプルコードはGithubにあります。