Help us understand the problem. What is going on with this article?

Spring 4.3 テスト関連の主な変更点

More than 3 years have passed since last update.

今回は、Spring Framework 4.3の変更点紹介シリーズの最終回で、テスト関連の変更点を紹介します。
(リリース前の投稿を更新しました!! 差分は、「★6/11追加」でマークしてあります)

シリーズ

動作検証環境

  • Spring Framework 4.3.0.RELEASE
  • Spring Boot 1.4.0.BUILD-SNAPSHOT (2016/6/11時点)

Testing Improvements

今回は、テスト関連の主な変更点をみていきます。

No テスト関連の主な変更点
1 Spring TestContext FrameworkのJUnitの必須バージョンが4.12以上になります。
2 SpringJUnit4ClassRunnerクラスの別名としてSpringRunnerクラスが追加されます。
3 Spring Testが提供しているテスト用のアノテーションをインタフェースに定義することで、複数のテストクラスでアノテーションの定義を共有できるようになります。 「★6/11追加」
4 自動検出されるテスト用のBean定義ファイルを利用する場合は、@ContextConfigurationは省略できるようになります。
5 @Transactionalは、public以外のテストメソッドにも適用できます。(TestNGやJUnit 5向けの対応)
6 @BeforeTransaction@AfterTransactionは、public以外のテストメソッドにも指定できるようになり、さらにJava 8からサポートされたインタフェースのデフォルトメソッドに指定することができます。
7 Spring TestContext Framework内でキャッシュするアプリケーションコンテキストの数に上限を設けられるようになります。デフォルトの動作では、キャッシュの最大数は32個で最大数をこえると最も使われていないものから破棄されていきます。なお、キャッシュの最大数は、プロパティ(spring.test.context.cache.maxSize)で変更できます。
8 Bean定義をカスタマイズするためのインタフェース(ContextCustomizer)が追加され、アプリケーションコンテキスト内にBean定義を読み込んだ直後にコールバックされます。
9 @Sql@SqlGroupをメタアノテーションとして利用できるようになります。
10 ReflectionTestUtilsにて、インタフェースベースのProxyオブジェクトに対するフィールドアクセスが可能になります。 「★6/11追加」
11 サーバーサイド向けSpring MVC Test Framework(MockMvc)にて、複数の値をもつレスポンスヘッダの検証がサポートされます。
12 サーバーサイド向けSpring MVC Test Framework(MockMvc)にて、リクエストボディに指定したフォームデータ(application/x-www-form-urlencoded形式)がリクエストパラメータとして扱われるようになります。
13 サーバーサイド向けSpring MVC Test Framework(MockMvc)にて、呼び出されたHandlerメソッドの検証を行うことができるようになります。 「★6/11追加」
14 クライアントサイド向けモックRESTサーバ機能(MockRestServiceServer)にて、モックレスポンスを返却する回数などを指定できるようになります。
15 クライアントサイド向けモックRESTサーバ機能(MockRestServiceServer)にて、リクエストボディに指定したフォームデータ(application/x-www-form-urlencoded形式)の検証がサポートされます。

JUnitの必須バージョンが4.12以上になる

Spring 4.3から、Junitを使う場合は4.12以上のバージョンが必要になります。Spring Bootの最新バージョン(1.3.5.RELEASE)はすでに4.12に対応しているため、Spring Bootユーザーは意識する必要はないでしょう。
Spring Bootユーザーではない方や、独自にJUnitの依存定義を行っている場合は、JUnitのバージョンを変更してください。

pom.xml
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

SpringRunnerクラスが追加される :thumbsup:

Spring 4.3から、SpringJUnit4ClassRunnerの別名クラスとしてSpringRunnerクラスが追加されます。名前が短くてナイスです :thumbsup: 利用できる機能はSpringJUnit4ClassRunnerと全く一緒で、SpringJUnit4ClassRunnerも引き続き利用可能です。

〜4.2
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringTest {
    // ...
}
4.3〜
@RunWith(SpringRunner.class) // 名前が短くてシンプル!!
public class SpringTest {
    // ...
}

テスト用のアノテーションをインタフェースに定義できる :thumbsup:

Spring 4.3から、Spring Testが提供しているテスト用のアノテーションをインタフェースに定義することで、複数のテストクラスでアノテーションの定義を共有できるようになります。
インタフェースに定義可能なアノテーションについては、Spring JIRAのSPR-14184を参照してください!!

アノテーションを付与したインタフェースの作成例
package com.example;

import org.springframework.test.context.ActiveProfiles;

@ActiveProfiles("unittest") // インタフェースにアノテーションを付与する
public interface ActivateUnitTestProfiles {
}
インタフェースを実装したテストクラスの作成例
package com.example;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.test.context.junit4.SpringRunner;

import java.time.Clock;
import java.time.ZoneId;
import java.time.ZonedDateTime;

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

@RunWith(SpringRunner.class)
public class ProfileTests implements ActivateUnitTestProfiles { // インタフェースを実装する

    @Autowired
    Clock clock;

    @Test
    public void testClock() {
        assertThat(ZonedDateTime.now(clock))
                .isEqualTo(ZonedDateTime.of(2016, 6, 11, 0, 0, 0, 1, ZoneId.systemDefault()));
    }

}
Profileを意識したコンフィギュレーションクラス
@Configuration
public class ClockConfig {
    @Bean
    @Profile("prod")
    public Clock prodClock() {
        return Clock.systemDefaultZone();
    }

    @Bean
    @Profile("dev")
    public Clock devClock() {
        return Clock.systemDefaultZone();
    }

    @Bean
    @Profile("unittest") //  このBean定義が使われる
    public Clock unittestClock() {
        return Clock.fixed(ZonedDateTime.of(2016, 6, 11, 0, 0, 0, 1, ZoneId.systemDefault()).toInstant(), ZoneId.systemDefault());
    }

}

インタフェースなので、目的毎(例えば、Web用、バッチ用、データアクセス用など)に作成したインタフェースをミックスインして利用することもできます。また、Java 8から追加されたデフォルトメソッドの仕組みを利用してユーティリティ的なメソッドを用意することができるため、テストクラスでは、テストケースの作成に集中しやすくなります。

なお、それぞれのアノテーションのサンプルは、Spring Testの「テストケースクラスのソースコード」が参考になります。

@ContextConfigurationが省略できる :thumbsup:

Spring 4.3から、@ContextConfigurationが省略できるようになります。@ContextConfigurationを省略する場合は、Springが自動検出するBean定義(クラス or ファイル)を用意する必要があります。(ちなみに・・・用意しなくてもエラーにはなりませんが、DIコンテナにテスト対象のBeanなどが登録できないので意味ないですよね :sweat_smile:
この仕組みは、本格的なテストを行う時にはあまり使う機会はない気もしますが・・・ちょっとした動作検証をJUnit上で行う時に非常に便利で、私もよく使います :smile:

Note: Spring 4.2までは・・・
同等の仕組みは提供されていますが、必ず@ContextConfigurationを付与する必要がありました。

Springは、以下のBean定義(クラス or ファイル)を自動検出しますが、併用はできません。

  • @Configurationが付与されたstaticインナークラス
  • ネーミングルールで解決されるXML形式のBean定義ファイル

@Configurationが付与されたstaticインナークラスの利用

テストケースクラス内に、Java Configクラスをstaticなインナークラスとして定義します。

テストケースクラス
@RunWith(SpringRunner.class)
// @ContextConfiguration ← Spring 4.3から省略可能
public class SpringTest {
    @Configuration
    static class LocalContext {
        @Bean
        public DataSource dataSource() {
            return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
        }
    }
    // ...
}

ネーミングルールで解決されるXML形式のBean定義ファイルの利用

テストケースクラスと同じディレクトリ階層に、「テストケースのクラス名-context.xml」という名前のXML形式のBean定義ファイルを配置します。

テストケースクラス
package com.example;
// ...
@RunWith(SpringRunner.class)
// @ContextConfiguration ← Spring 4.3から省略可能
public class SpringTest {
    // ...
}
src/test/resources/com/example/SpringTest-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xsi:schemaLocation="
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/jdbc
         http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
       ">

    <jdbc:embedded-database type="H2" id="dataSource"/>

</beans>

@Transactionalをpublic以外のテストメソッドにも適用できる :thumbsup:

Spring 4.3から、@Transactionalをpublic以外のテストメソッドに適用できるようになります。これは、「TestNG」や「JUnit 5」向けの対応のようです。本投稿では特に説明はしません。(あしからず・・・)
ちなみに・・・SpringのJUnit 5対応については「SPR-13575」をご覧ください。プロトタイププロジェクト (spring-test-junit5)がGitHub上に公開されており、Spring 5でSpring本体に組み込まれるみたいです。

@BeforeTransaction@AfterTransactionをインタフェースのデフォルトメソッドに適用できる :thumbsup:

Spring 4.3から、Java 8からサポートされたインタフェースのデフォルトメソッドに@BeforeTransaction@AfterTransactionを適用できるようになります。また、本投稿では説明はしませんが、@Transactionalと同様にpublic以外のテストメソッドにも適用できるようになります。

本投稿では、Java 8からサポートされたインタフェースのデフォルトメソッドで@BeforeTransaction@AfterTransactionを利用する方法をみていきます。

デフォルトメソッドを実装したインタフェースの実装例
package com.example;

import org.springframework.test.context.transaction.AfterTransaction;
import org.springframework.test.context.transaction.BeforeTransaction;

public interface TransactionSupports {
    @BeforeTransaction
    default void beforeTx() {
        System.out.println("before transaction !!");
        // Please implements your code
    }
    @AfterTransaction
    default void afterTx() {
        System.out.println("after transaction !!");
        // Please implements your code
    }
}

デフォルトメソッドを実装したインタフェースをテストケースクラスで実装することで、@BeforeTransactionメソッドと@AfterTransactionメソッドをテストケースクラスにミックスインすることができます。

@BeforeTransactionと@AfterTransactionメソッドのミックスイン例
@RunWith(SpringRunner.class)
@Transactional
public class SpringTest implements TransactionSupports { // デフォルトメソッドを実装したインタフェースを実装するだけ!!
    // ...
}

この仕組みを利用すると、@BeforeTransactionメソッドと@AfterTransactionメソッドの実装を簡単に複数のクラスで共有できます。抽象クラスではなくインタフェースなので、複数のインタフェースに実装されているメソッドをミックスインもできます!!

アプリケーションコンテキストのキャッシュ数に上限を設けることができる :thumbsup:

Spring TestContext Framework内でキャッシュするアプリケーションコンテキストの数に上限を設けられるようになります。デフォルトの動作では、キャッシュの最大数は32個で最大数をこえると最も使われていないものから破棄(LRU : Least Recently Used)されていきます。なお、キャッシュの最大数は、プロパティ(spring.test.context.cache.maxSize)で変更できます。

プロパティ値はプロパティファイルとシステムプロパティで指定できますが、指定が重複している場合はプロパティファイルの値が優先されます。

Springプロパティファイルを利用して最大値を変更

クラスパス直下にspring.propertiesを作成して最大値を指定します。

src/test/resources/spring.properties
spring.test.context.cache.maxSize=64

システムプロパティを利用して最大値を変更

-Dspring.test.context.cache.maxSize=64

Bean定義をカスタマイズするためのインタフェース(ContextCustomizer)が追加される :thumbsup:

Spring 4.3から、Bean定義をカスタマイズするためのインタフェース(ContextCustomizer)が追加され、アプリケーションコンテキスト内にBean定義を読み込んだ直後にコールバックされます。このインタフェースの実装クラスを作成することで、テスト用のアプリケーションコンテキストをカスタマイズできます。なお、Spring BootはContextCustomizerの仕組みを利用して、テスト用のアプリケーションコンテキストをSpring Bootアプリケーションのテスト向けにカスタマイズしているようです。

では、自作のContextCustomizerをSpring TestContext Frameworkに適用する方法をみていきましょう。まず、ContextCustomizerインタフェースの実装クラスを作成します。

package com.example;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration;

public class MyContextCustomizer implements ContextCustomizer {
    @Override
    public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
        System.out.println("customizeContext !!");
        // Please implements your code.
    }
}

次に、作成したMyContextCustomizerクラスのインスタンスを生成するためのファクトリクラスを作成します。

package com.example;

import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;

import java.util.List;

public class MyContextCustomizerFactory implements ContextCustomizerFactory {
    @Override
    public ContextCustomizer createContextCustomizer(Class<?> testClass, List<ContextConfigurationAttributes> configAttributes) {
        return new MyContextCustomizer();
    }
}

最後に、クラスパス上の/META-INF/spring.factoriesに作成したファクトリクラスを登録します。(ファイルがなければ作成してください)

src/test/resoueces/META-INF/spring.factories
# Spring Test ContextCustomizerFactories
org.springframework.test.context.ContextCustomizerFactory=com.example.MyContextCustomizerFactory

@Sql@SqlGroupをメタアノテーションとして利用できる :thumbsup:

Spring 4.3から、@Sql@SqlGroupをメタアノテーションとして利用できるようになります。
ここでは、各優先度が高いメッセージのみを扱うリスナーであることを表現するカスタムアノテーションを使ってみます。

カスタムアノテーション(Java8〜)
@Sql("classpath:sql/clear.sql")
@Sql("classpath:sql/load.sql")
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface InitializingSqls {
}
カスタムアノテーション(〜Java7)
@SqlGroup({
    @Sql("classpath:sql/clear.sql")
    , @Sql("classpath:sql/load.sql")
})
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface InitializingSqls {
}

テストケースクラス側では、クラスレベルまたはメソッドレベルにカスタムアノテーションを指定するだけです。

テストケースクラス
@RunWith(SpringRunner.class)
@Transactional
@InitializingSqls // カスタムアノテーションを指定
public class SpringTests implements TransactionSupports {
    // ...
}

インタフェースベースのProxyオブジェクトに対するフィールドアクセスが可能になる :thumbsup:

Spring 4.3にて、テスト用のユーティリティクラス(ReflectionTestUtils)を介してインタフェースベースのProxyオブジェクトに対するフィールドアクセスが可能になります。個人的には、ReflectionTestUtilsを使わなくても済むようにクラス設計、テスト設計すべきだと思いますが・・・ :sweat_smile: (ま、Spring Testにはこんなユーティリティもあるんだ〜くらいに思っておけばいいかと思います・・・)

package com.example;

public interface TodoService {
    void create(Todo todo);
}
package com.example;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

@Service
@Transactional // インタフェースベースのproxyオブジェクトになる
public class TodoServiceImpl implements TodoService {
    @Autowired
    private RestOperations restOperations;
    public void create(Todo todo) {
        // ...
    }
}
@Autowired
TodoService todoService; // Proxyオブジェクトがインジェクションされる

@Test
public void createTodo() {
    RestOperations restOperations = Mockito.mock(RestOperations.class);
    // ...
    ReflectionTestUtils.setField(todoService, "restOperations", restOperations); // フィールドアクセスしてモックを設定

    todoService.create(new Todo());
    // ...

}

MockMvcで複数の値をもつレスポンスヘッダの検証ができる :thumbsup:

Spring 4.3から、サーバーサイド向けSpring MVC Test Framework(MockMvc)にて、複数の値をもつレスポンスヘッダの検証用メソッドが追加されます。

レスポンスヘッダ
...
Cache-Control: max-age=86400
Cache-Control: must-revalidate
...
テストケースクラス
@RunWith(SpringRunner.class)
@WebAppConfiguration
public class SpringTest {

    @Autowired
    WebApplicationContext wac;

    MockMvc mockMvc;

    @Before
    public void setUpMockMvc() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    @Test
    public void getMultiResponseHeader() throws Exception {
        mockMvc.perform(get("/"))
                .andExpect(header()
                        .stringValues("Cache-Control", "max-age=86400", "must-revalidate")); // 複数のヘッダ値の検証が可能
    }

}

MockMvcでフォーム形式のボディをリクエストパラメータとして扱える :thumbsup:

Spring 4.3から、サーバーサイド向けSpring MVC Test Framework(MockMvc)にて、リクエストボディに指定したフォームデータ(application/x-www-form-urlencoded形式)がリクエストパラメータとして扱われるようになります。 ここでは、param1param2というふたつのリクエストパラメータを扱う例をみてみましょう。

@Controller
public class TestController {
    @RequestMapping(path = "/post", method = RequestMethod.POST)
    public String post(@RequestParam String param1, @RequestParam String param2, Model model) {
        model.addAttribute("param1", param1);
        model.addAttribute("param2", param2);
        return "confirm";
    }
}

Spring 4.2までは、paramメソッドを使ってリクエストパラメータを指定する必要がありました。もちろんSpring 4.3以降でもこの方法は利用できます。

@Test
public void form() throws Exception {
    mockMvc.perform(post("/post")
            .param("param1", "aaaa").param("param2", "bbbb")) // paramメソッドを使ってリクエストパラメータの指定
            .andExpect(status().isOk())
            .andExpect(view().name("confirm"))
            .andExpect(model().attribute("param1", "aaaa"))
            .andExpect(model().attribute("param2", "bbbb"));
}

Spring 4.3からは、contentメソッドにapplication/x-www-form-urlencoded形式のボディを指定することで同じことを実現することができます。

4.3〜
@Test
public void form() throws Exception {
    mockMvc.perform(post("/post")
            .contentType(MediaType.APPLICATION_FORM_URLENCODED) // Content-Typeに「application/x-www-form-urlencoded」を指定
            .content("param1=aaaa&param2=bbbb")) // リクエストボディに「application/x-www-form-urlencoded」形式の値を指定
            .andExpect(status().isOk())
            .andExpect(view().name("confirm"))
            .andExpect(model().attribute("param1", "aaaa"))
            .andExpect(model().attribute("param2", "bbbb"));
}

Content-TypeにMediaType.APPLICATION_FORM_URLENCODEDを指定するのがポイントです。指定しないと、リクエストパラメータとして扱われません。

呼び出されたHandlerメソッドの検証ができる :thumbsup:

Spring 4.3から、サーバーサイド向けSpring MVC Test Framework(MockMvc)にて、呼び出されたHandlerメソッドの検証を行うことができるようになります。

@Test
public void getAll() throws Exception {
    mockMvc.perform(get("/todos"))
            .andExpect(status().isOk())
            // 呼び出されたメソッドを検証(MvcUriComponentsBuilderのonメソッドを利用)
            .andExpect(handler().methodCall(on(TodoRestController.class).getAll()))
            // 呼び出されたHandlerメソッドのクラスを検証
            .andExpect(handler().handlerType(TodoRestController.class))
            // 呼び出されたメソッドを検証
            .andExpect(handler().method(TodoRestController.class.getMethod("getAll")))
            // 呼び出されたメソッドの名前を検証
            .andExpect(handler().methodName("getAll"));
}

いくつか方法が用意されていますが、MvcUriComponentsBuilderを使う方法がタイプセーフな実装なのでオススメです。

MockRestServiceServerでモックレスポンスを返却する回数を指定できる :thumbsup:

Spring 4.3から、クライアントサイド向けモックRESTサーバ機能(MockRestServiceServer)にて、モックレスポンスを返却する回数や順序性を無視するためのフラグなどを指定できるようになります。 MockRestServiceServerはあまり知られていない!?気がしますが、外部のREST APIなどにアクセスするようなアプリケーションのテストを行う場合に非常に便利な機能です。

MockRestServiceServerの使い方

すご〜く簡単にMockRestServiceServerの使い方を紹介しておきます。たとえば、以下のようなControllerのテストを考えてみましょう。このControllerクラスでは、外部のREST APIを呼び出した結果を画面に表示しています。外部のREST APIを呼び出す際には、Springが提供しているRestOperationsインターフェースを介してRestTemplateクラスを利用します。

テスト対象のControllerクラス
@RequestMapping("/todos")
@Controller
static class TodoController {

    @Autowired
    RestOperations restOperations;

    @RequestMapping(path = "{todoId}", method = RequestMethod.GET)
    public String detail(@PathVariable String todoId, Model model) {
        Todo todo = restOperations.getForObject("https://api.domain/todos/{todoId}", Todo.class, todoId); // 外部のREST APIの呼び出し
        model.addAttribute(todo);
        return "detail";
    }
}

何も行わないと、外部のREST APIにアクセスしてしまいます。システムテストなどでは外部のREST APIに実際につなぐ必要がありますが、単体テストやモジュール結合テストでは外部のREST APIにつなぎたくないケースもあります。そういった場合には、MockRestServiceServerが使えます。

テストケースクラス
@RunWith(SpringRunner.class)
@WebAppConfiguration
// ...
public class SpringTest {

    @Autowired
    WebApplicationContext wac;

    MockMvc mockMvc;

    @Autowired
    RestTemplate restTemplate; // テスト対象のクラスで使うRestTemplateと同じものをインジェクションする

    @Before
    public void setUpMockMvc() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    @Test
    public void detail() throws Exception {
        // 外部のAPIを呼び出す際に使用するRestTemplateを、MockRestServiceServerと紐づける。
        // こうすることで、MockRestServiceServerにリクエストが送られるようになる。
        MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();

        // 期待結果をMockRestServiceServerに登録する
        mockServer.expect(requestTo("https://api.domain/todos/123"))
                .andRespond(withSuccess("{\"id\":\"123\", \"title\":\"タイトル\", \"finished\":false}", MediaType.APPLICATION_JSON_UTF8));

        mockMvc.perform(get("/todos/123"))
                .andExpect(status().isOk())
                .andExpect(view().name("detail"))
                .andExpect(model().attribute("todo", hasProperty("id", is("123"))))
                .andExpect(model().attribute("todo", hasProperty("title", is("タイトル"))))
                .andExpect(model().attribute("todo", hasProperty("finished", is(false))));

        mockServer.verify(); // 期待通りのリクエストがMockRestServiceServerに送信されたか検証する
    }

}

さらに詳しい使い方を知りたい方は「Springの公式リファレンス」をご覧ください。

2016/6/6 追記
SpringのRestTemplateを使うコンポーネントのJUnitテストはこう書く!!」にMockRestServiceServerの仕組みと使い方をまとめました。

モックレスポンスを返却する回数の制御

で、Spring 4.3からexpectメソッドの第一引数に任意のExpectedCountオブジェクトを指定することで、指定したモックレスポンスを返却する回数を指定できるようになります。

〜4.2
// 地味に列挙する必要があったが・・・
MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
mockServer.expect(requestTo("https://api.domain/todos/123"))
            .andRespond(withSuccess("{\"id\":\"123\", \"title\":\"タイトル\", \"finished\":false}", 
mockServer.expect(requestTo("https://api.domain/todos/123"))
            .andRespond(withSuccess("{\"id\":\"123\", \"title\":\"タイトル\", \"finished\":false}", 
mockServer.expect(requestTo("https://api.domain/todos/123"))
            .andRespond(withSuccess("{\"id\":\"123\", \"title\":\"タイトル\", \"finished\":false}", 
// ...
4.3〜
MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
// 回数指定が可能になる!!
mockServer.expect(times(3), requestTo("https://api.domain/todos/123"))
            .andRespond(withSuccess("{\"id\":\"123\", \"title\":\"タイトル\", \"finished\":false}", 
// ...

times意外にも、once, manyTimes, min, max, betweenメソッドが用意されています。

順序性の無視

Spring 4.3から、expectメソッドを使って指定した期待結果(モックレスポンス)の順序性を検証対象から除外する(無視する)ことを示すフラグ(ignoreExpectOrder)が追加されます。ignoreExpectOrdertrueにすると、定義順に関係なくリクエスト内容に一致する期待結果(モックレスポンス)が消費されます。

MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate)
        .ignoreExpectOrder(true).build();

bindToメソッド

ちなみに・・・MockRestServiceServerbindToメソッドはSpring 4.3から追加されたメソッドで、MockRestServiceServer用のビルダーオブジェクトを返却します。MockRestServiceServerのオプションを変更する必要がなければ、MockRestServiceServercreateServerメソッドを使うこともできます。

MockRestServiceServerでフォーム形式のリクエストボディの検証ができる :thumbsup:

Spring 4.3から、クライアントサイド向けモックRESTサーバ機能(MockRestServiceServer)にて、リクエストボディに指定したフォームデータ(application/x-www-form-urlencoded形式)の検証ができるようになります。

たとえば、外部のWeb APIにフォームデータ(application/x-www-form-urlencoded形式)を送信するような処理があったとします。

テスト対象のControllerクラス
@RequestMapping("/todos")
@Controller
public class TodoController {

    @Autowired
    RestOperations restOperations;

    @RequestMapping(method = RequestMethod.POST)
    public String postTodo(Form from) {

        // フォームデータ(application/x-www-form-urlencoded形式)を送信
        MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
        formData.add("title", from.getTitle());
        restOperations.postForObject("https://api.domain/todos", formData, Void.class);

        return "complete";
    }

}

この処理に対するテストは、以下のように書くことができます。

テストケースメソッド
@Test
public void postTodo() throws Exception {

    MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();

    MultiValueMap<String, String> expectedFormData = new LinkedMultiValueMap<>();
    formData.add("title", "タイトル");

    mockServer.expect(requestTo("https://api.domain/todos"))
            .andExpect(content().contentType(MediaType.APPLICATION_FORM_URLENCODED))
            .andExpect(content().formData(expectedFormData)) // リクエストデータの期待値を指定
            .andRespond(withCreatedEntity(URI.create("https://api.domain/todos/123")));

    mockMvc.perform(post("/todos")
            .param("title", "タイトル"))
            .andExpect(status().isOk())
            .andExpect(view().name("complete"));

    mockServer.verify();
}

まとめ

今回は、テスト関連の主な変更点を紹介しました。今回でSpring Framework 4.3の変更点紹介シリーズはおわりですが、本シリーズで紹介していない変更点も数多くあります。その中には、本シリーズで紹介してものより利用頻度が多そう!?なものもあるので、興味がある方は是非「Spring JIRAのIssue」を確認してみてください。

いったん終了しますが、継続して更新していきたいと思います。

参考サイト

補足

Spring 4.3 GAに伴い変更点を追加 (2016/6/11)

ついに4.3がGAになり、そのタイミングで主な変更点に追加されたトピックスを反映しました。(「★6/11追加」でマークしてあります)

kazuki43zoo
Javaエンジニアで、SpringやMyBatisらへんにそれなりに詳しいです。お仕事のつながりで「Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発」を共著させてもらいました!
https://kazuki43zoo.github.io
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした