Spring BootでTestRestTemplateを試す

Spring BootでTestRestTemplateを試してみたメモです。

TestRestTemplate

httpクライアントとしてRestTemplateがあります。
TestRestTemplateはRestTemplateのテスト用のクラスで、ベーシック認証のサポートなどテスト用に便利になっているようです。
Spring Boot1.4からRestTemplateを継承しなくなったようです。

http通信はデフォルトではJava標準のHttpURLConnectionが使われるようです。

今回テストのRESTクライアントとして使ってみます。

TestRestTemplate (Spring Boot Docs 1.5.3.RELEASE API)

テスト対象クラス

テスト対象のcontrollerとして下記のクラスを用意しました。
GET、POST、PUT、DELETEを受け付けます。

PeopleController.java

package com.example.contoroller;

import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class PeopleController {
    @ResponseBody
    @RequestMapping(value = "/api/people", method = RequestMethod.GET)
    public People getPeople() {
        People people = new People();
        people.setCountry("Japan");
        people.setYear(2001);
        people.setPopulation(1_000_000);
        return people;
    }

    @ResponseBody
    @RequestMapping(value = "/api/people", method = RequestMethod.POST)
    public int postPeople(@RequestBody People people) {
        // 登録処理
        // 登録件数を返す
        return 1;
    }

    @ResponseBody
    @RequestMapping(value = "/api/people", method = RequestMethod.PUT)
    public int putPeople(@RequestBody People people) {
        // 更新処理
        // 更新件数を返す
        return 2;
    }

    @ResponseBody
    @RequestMapping(value = "/api/people/{country}", method = RequestMethod.DELETE)
    public int deletePeople(@PathVariable String country) throws Exception {
        // 削除処理
        // 削除件数を返す
        return 3;
    }

    @Data
    @NoArgsConstructor
    @FieldDefaults(level = AccessLevel.PRIVATE)
    public static class People {
        String country;
        int year;
        int population;
    }
}

GETメソッドはPeopleオブジェクトを返します。

    @ResponseBody
    @RequestMapping(value = "/api/people", method = RequestMethod.GET)
    public People getPeople() {
        People people = new People();
        people.setCountry("Japan");
        people.setYear(2001);
        people.setPopulation(1_000_000);
        return people;
    }

POSTメソッドは、RequestBodyでPeopleオブジェクトを受け取り、登録件数を返します。

    @ResponseBody
    @RequestMapping(value = "/api/people", method = RequestMethod.POST)
    public int postPeople(@RequestBody People people) {
        // 登録処理
        // 登録件数を返す
        return 1;
    }

PUTメソッドは、RequestBodyでPeopleオブジェクトを受け取り、更新件数を返します。

    @ResponseBody
    @RequestMapping(value = "/api/people", method = RequestMethod.PUT)
    public int putPeople(@RequestBody People people) {
        // 更新処理
        // 更新件数を返す
        return 2;
    }

DELETEメソッドは、PathVariableで削除対象の国名を受け取り、削除件数を返します。

    @ResponseBody
    @RequestMapping(value = "/api/people/{country}", method = RequestMethod.DELETE)
    public int deletePeople(@PathVariable String country) throws Exception {
        // 削除処理
        // 削除件数を返す
        return 3;
    }

テストコード

TestRestTemplateを使ってテストしてみました。

テストクラスとして
(1),(2) @RunWith(SpringRunner.class)と@SpringBootTestを指定します。
(1) Spring Boot1.4から@RunWith(SpringJUnit4ClassRunner.class)が@RunWith(SpringRunner.class)に変わりました。
(2) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)で
  空いているランダムなポートでテストを行います。

@RunWith(SpringRunner.class) // (1)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // (2)
public class PeopleControllerTest {

(1) @AutoWiredでTestRestTemplateをインジェクトして、
(2) @LocalServerPortで割り当てられたポートをインジェクトします。

    @Autowired // (1)
    private TestRestTemplate testRestTemplate;

    @LocalServerPort // (2)
    private int port;
GETのテスト

GETの戻り値がオブジェクトの場合、
TestRestTemplate.getForEntity(String url, Class<T> responseType, Object... urlVariables)を使用します。
urlでRESTのAPIを指定して、responseTypeでAPIの戻り値のクラスを指定します。
パス変数を指定する場合はurlVariablesを可変長で設定します。

@Test
public void getPeopleTest() {
    String url = "http://localhost:" + port + "/api/people";
    People people = testRestTemplate.getForObject(url, People.class);

    People expected = new People();
    expected.setCountry("Japan");
    expected.setYear(2001);
    expected.setPopulation(1_000_000);

    assertThat(people).isEqualTo(expected);
}
POSTのテスト

POSTで任意の戻り値を受けるには、
TestRestTemplate.postForObject(String url, Object request, Class<T> responseType, Object... urlVariables)を使用します。
urlでREST APIのURLを指定して、requestでRequestBodyのオブジェクトを指定します。
responseTypeでAPIの戻り値のクラスを指定します。
パス変数を指定する場合はurlVariablesを可変長で設定します。

@Test
public void postPeopleTest() {
    String url = "http://localhost:" + port + "/api/people";
    People people = new People();
    people.setCountry("America");
    people.setYear(2002);
    people.setPopulation(2_000_000);

    int result = testRestTemplate.postForObject(url, people, Integer.class);
    assertThat(result).isEqualTo(1);
}
PUTのテスト

(1) PUTのテストではTestRestTemplate.put(String url, Object request, Object... urlVariables)が使用できます。
  urlでREST APIのURLを指定して、requestでRequestBodyのオブジェクトを指定します。
  パス変数を指定する場合はurlVariablesを可変長で設定します。
  しかし、TestRestTemplate.put()は戻り値がvoidのため、REST API側で戻り値を返す場合、取得できません。

戻り値を取得したい場合TestRestTemplate.exchange()を使用すると、
ResponseEntityが返るのでREST APIの戻り値を取得することができます。
(2) RequestEntityを生成します。RequestEntity<People>でbodyの型を記述し、
  putでRESTのPUT APIを指定し、bodyでRequestBodyのオブジェクトを指定します。

(3) exchange()でRequestEntityと戻り値のクラスを指定します。
  戻り値としてResponseEntityが返ります。ResponseEntity.getBody()でAPIの戻り値を取得できます。

@Test
public void putPeopleTest() throws URISyntaxException {
    String url = "http://localhost:" + port + "/api/people";
    People people = new People();
    people.setCountry("America");
    people.setYear(2002);
    people.setPopulation(2_000_000);

    // put
    testRestTemplate.put(url, people); // (1)

    // exchange
    URI uri = new URI(url);
    RequestEntity<People> requestEntity = RequestEntity // (2)
            .put(uri)
            .body(people);

    ResponseEntity<Integer> result = testRestTemplate.exchange(requestEntity, Integer.class); // (3)

    assertThat(result.getBody()).isEqualTo(2);
}
DELETEのテスト

(1) DELETEのテストではTestRestTemplate.delete(String url, Object... urlVariables)が使用できます。
  urlでREST APIのURLを指定して、urlVariablesでパス変数を可変長で設定します。
  しかし、TestRestTemplate.delete()は戻り値がvoidのため、REST API側で戻り値を返す場合、取得できません。

戻り値を取得したい場合TestRestTemplate.exchange()を使用すると、
ResponseEntityが返るのでREST APIの戻り値を取得することができます。
(2) DELETEの場合、pathにcountryを指定する必要があるので、
  exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... urlVariables)
  を使用します。

  引数として、urlでRESTのDELETE APIを指定し、methodでHttpMethod.DELETEを指定します。
  requestEntityでHttpEntityのオブジェクトを指定します。http headerを設定する場合はここで指定しますが、
  今回は不要なのでHttpEntity.EMPTYを指定しています。
  responseTypeで戻り値の型を指定します。
  urlVariablesでパス変数を指定します。今回はcountryを指定します。

  戻り値としてResponseEntityが返ります。ResponseEntity.getBody()でAPIの戻り値を取得できます。

@Test
public void deletePeopleTest() {
    String url = "http://localhost:" + port + "/api/people/{country}";

    String country = "america";

    // delete
    testRestTemplate.delete(url, country);

    // exchange
    ResponseEntity<Integer> result = testRestTemplate.exchange(url, // (2)
            HttpMethod.DELETE,
            HttpEntity.EMPTY,
            Integer.class,
            country);

    assertThat(result.getBody()).isEqualTo(3);
}

今回のテストコードの全体です。

PeopleControllerTest.java

package com.example.contoroller;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.embedded.LocalServerPort;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import com.example.contoroller.PeopleController.People;

import java.net.URI;
import java.net.URISyntaxException;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PeopleControllerTest {
    @Autowired
    private TestRestTemplate testRestTemplate;

    @LocalServerPort
    private int port;

    @Test
    public void getPeopleTest() {
        String url = "http://localhost:" + port + "/api/people";
        People people = testRestTemplate.getForObject(url, People.class);

        People expected = new People();
        expected.setCountry("Japan");
        expected.setYear(2001);
        expected.setPopulation(1_000_000);

        assertThat(people).isEqualTo(expected);
    }

    @Test
    public void postPeopleTest() {
        String url = "http://localhost:" + port + "/api/people";
        People people = new People();
        people.setCountry("America");
        people.setYear(2002);
        people.setPopulation(2_000_000);

        int result = testRestTemplate.postForObject(url, people, Integer.class);
        assertThat(result).isEqualTo(1);
    }

    @Test
    public void putPeopleTest() throws URISyntaxException {
        String url = "http://localhost:" + port + "/api/people";
        People people = new People();
        people.setCountry("America");
        people.setYear(2002);
        people.setPopulation(2_000_000);

        // put
        testRestTemplate.put(url, people);

        // exchange
        URI uri = new URI(url);
        RequestEntity<People> requestEntity = RequestEntity
                .put(uri)
                .body(people);

        ResponseEntity<Integer> result = testRestTemplate.exchange(requestEntity, Integer.class);

        assertThat(result.getBody()).isEqualTo(2);
    }

    @Test
    public void deletePeopleTest() {
        String url = "http://localhost:" + port + "/api/people/{country}";

        String country = "america";

        // delete
        testRestTemplate.delete(url, country);

        // exchange
        ResponseEntity<Integer> result = testRestTemplate.exchange(url,
                HttpMethod.DELETE,
                HttpEntity.EMPTY,
                Integer.class,
                country);

        assertThat(result.getBody()).isEqualTo(3);
    }
}

ソースは一応あげときました。

github.com


終わり。

【参考】
spring徹底入門を書いている清水さんの記事がかなり詳しいです。
SpringのRestTemplateを使うコンポーネントのJUnitテストはこう書く!! - Qiita