10.2.4. 単体テストで利用するOSSライブラリの使い方

Caution

本バージョンの内容は既に古くなっています。最新のガイドラインはこちらからご参照ください。

本節では、単体テストで利用するOSSライブラリとして、Spring Test(MockMvc)、Mockitoについて説明する。

10.2.4.1. Spring Test

10.2.4.1.1. Spring Test とは

Spring Testとは、Spring Framework上で動作するアプリケーションのテストを支援するモジュールである。 テストには、対象クラスが依存しているクラスをモックやスタブで代用して行うテストと、SpringのDIコンテナや実際の依存クラスと 組み合わせて行うテストがある。

本ガイドラインでは、モックを使用してテスト対象クラス単体でテストを行う方法と、 設定ファイルや実際の依存クラスを組み合わせてテストを行う方法の2通りの実装方法を例示する。

Spring Testでは主に以下の機能が提供されている。

  • テスティングフレームワーク(JUnit)上でSpringのDIコンテナを動かす機能
  • テストデータをセットアップする機能
  • アプリケーションサーバ上にデプロイせずに、Spring MVCの動作を再現する機能
  • テストに最適なトランザクション管理機能

その他、様々なSpring固有のアノテーションや、単体テストで利用するAPIが提供されている。

Spring Testは、テスティングフレームワーク上で動作するテスト用のフレームワーク機能として、 Spring TestContext Frameworkを提供している。

Spring TestContext Frameworkの処理フロー図を以下に示す。

../../_images/UsageOfLibraryForTestSpringTestProcessFlow.png
項番 説明
(1)
テスト実行により、org.springframework.test.context.junit4.SpringJUnit4ClassRunnerクラスが呼び出される。
(2)
SpringJUnit4ClassRunnerクラスはorg.springframework.test.context.TestContextManagerクラスを 生成する。
(3)
TestContextManagerクラスはorg.springframework.test.context.TestContextBootstrapperインタフェース のorg.springframework.test.context.TestContextインタフェースのビルド処理を呼び出す。
(4)
TestContextBootstrapperクラスはテストクラスで指定された設定ファイルをマージする org.springframework.test.context.MergedContextConfigurationクラスのビルド処理を呼び出す。
この時、テストクラスに明示的にブートストラップが指定されていない場合、@WebAppConfigurationがあれば org.springframework.test.context.web.WebTestContextBootstrapperクラス、指定されていなければ org.springframework.test.context.support.DefaultTestContextBootstrapperクラスが呼び出される。
(5)
MergedContextConfigurationクラスのビルド処理でorg.springframework.test.context.SmartContextLoaderインタフェースの実装クラスが呼び出される。
(6)
ブートストラップにWebTestContextBootstrapperクラスが使用されている場合は org.springframework.test.context.web.WebDelegatingSmartContextLoaderクラス、 DefaultTestContextBootstrapperクラスが使用されている場合は org.springframework.test.context.support.DelegatingSmartContextLoaderクラスがSmartContextLoaderインタフェースの実装クラスとして呼び出される。 SmartContextLoaderインタフェースの実装クラスでテストクラスの@ContextConfigurationで指定された ApplicationContextをロードする。
(7)
ブートストラップで取得したMergedContextConfigurationクラスを使用して org.springframework.test.context.TestContextインタフェースの実装クラスである org.springframework.test.context.support.DefaultTestContextクラスを生成する。
(8)
TestContextManagerクラスにテストクラスの@TestExecutionListenersで指定された org.springframework.test.context.TestExecutionListenerインタフェースを登録し、以下のエントリポイントで TestExecutionListenerの処理を呼び出す。
  • テストケースの全テストメソッド実行前(@BeforeClass
  • テストインスタンスの生成後
  • 各テストメソッドの実行前(@Before
  • 各テストメソッドの実行後(@After
  • テストケースの全テストメソッド実行後(@AfterClass
トランザクションの管理やテストデータのセットアップ処理は、TestExecutionListenerの処理によって行われる。
TestExecutionListenerの登録についてはTestExecutionListenerの登録を参照されたい。

10.2.4.1.1.1. Spring TestのDI機能

テストケースの@ContextConfigurationに設定ファイルを指定すると、SpringJUnit4ClassRunnerにデフォルトで 設定されているDependencyInjectionTestExecutionListenerの処理によってテスト実行時にSpringのDI機能を利用することが できる。

以下に@ContextConfigurationを使用して設定ファイルを読み込む例を示す。 ここでは、アプリケーションで使用するsample-infra.xmlを使用してテスト対象の com.example.domain.repository.member.MemberRepositoryをインジェクションしている。

  • sample-infra.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd
    ">

  <import resource="classpath:/META-INF/spring/sample-env.xml" />

    <!-- define the SqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:/META-INF/mybatis/mybatis-config.xml" />
    </bean>

    <!-- scan for Mappers -->
    <mybatis:scan base-package="com.example.domain.repository" />

</beans>
  • MemberRepositoryTest.java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
        "classpath:META-INF/spring/sample-infra.xml" }) //(1)
@Transactional
public class MemberRepositoryTest {

    @Inject
    MemberRepository target; // (2)
}
項番 説明
(1)
@ContextConfigurationsample-infra.xmlを指定する。
(2)
sample-infra.xmlに定義された<mybatis:scan>でBean登録されているMemberRepositoryを インジェクションする。

10.2.4.1.1.2. TestExecutionListenerの登録

テストケースに@TestExecutionListenersアノテーションを明示的に指定しない場合、Spring Testが提供している以下の org.springframework.test.context.TestExecutionListenerインタフェースの実装クラスがデフォルトで登録される。

なお、@TestExecutionListenersアノテーションを明示的に指定しない場合、デフォルトで登録される TestExecutionListenerはOrderを持っており、呼び出し順は下記表の順に固定されている。 TestExecutionListenerが個別に指定された場合は、指定された順番通りに呼び出される。

TestExecutionListenerの実装クラス 説明
ServletTestExecutionListener WebApplicationContextのテストをサポートするモックサーブレットAPIを設定する機能を提供している。
DirtiesContextBeforeModesTestExecutionListener テストで使用するDIコンテナのライフサイクル管理機能を提供している。 テストクラスまたはテストメソッドの実行前に呼び出される。
DependencyInjectionTestExecutionLiLstener テストで使用するインスタンスへのDI機能を提供している。
DirtiesContextTestExecutionListener テストで使用するDIコンテナのライフサイクル管理機能を提供している。 テストクラスまたはテストメソッドの実行後に呼び出される。
TransactionalTestExecutionListener テスト実行時のトランザクション管理機能を提供している。
SqlScriptsTestExecutionListener @Sqlアノテーションで指定されているSQLを実行する機能を提供している。

TestExecutionListenerの詳細はSpring Framework Documentation -TestExecutionListener Configuration-を参照されたい。

TestExecutionListenerは通常、デフォルト設定から変更する必要はないが、テストライブラリが独自に 提供しているTestExecutionListenerを使用する場合は@TestExecutionListenersアノテーションを使用して TestContextManagerに登録する必要がある。

ここでは例として、Spring Test DBUnitが提供するTransactionDbUnitTestExecutionListenerを登録する方法を説明する。

  • MemberRepositoryDbunitTest.java
@TestExecutionListeners({                               // (1)
        DirtiesContextBeforeModesTestExecutionListener.class,
        DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionDbUnitTestExecutionListener.class})  // (2)
@Transactional
public class MemberRepositoryDbunitTest {
項番 説明
(1)
クラスレベルに@TestExecutionListenersアノテーションを付けてTestExecutionListenerインタフェース の実装クラスを指定することで、テスト実行時に指定したTestExecutionListenerの処理を呼び出すことができる。 詳細は@TestExecutionListenersのJavadocを参照されたい。
(2)
TransactionDbUnitTestExecutionListenerはSpring Test DBUnitが提供するTestExecutionListenerインタフェースの実装クラスである。@DatabaseSetup@ExpectedDatabase@DatabaseTearDownなどのアノテーションを使用したデータのセットアップ、検証、後処理の機能を提供している。
TransactionDbUnitTestExecutionListenerは内部でTransactionalTestExecutionListenercom.github.springtestdbunit.DbUnitTestExecutionListenerをチェインしている。

Warning

DbUnitTestExecutionListenerの注意点

テストケース内で@Transactionalを指定せずにSpring Test DBUnitの提供するDbUnitTestExecutionListenerを 使用した場合、@DatabaseSetupなどのアノテーションのトランザクションと、テスト対象クラスのトランザクションは別に なるため、データのセットアップが反映されないなど正常に動作しない可能性があることに注意されたい。なお、テストケース内で @Transactionalを指定する場合はDbUnitTestExecutionListenerの代わりに TransactionDbUnitTestExecutionListenerが提供されているため、そちらを使用する必要がある。


10.2.4.2. MockMvc

MockMvcは、本来Spring Testの機能に含まれるが、 本章ではアプリケーション層の単体テストにおいて使用しているため、 Spring Testの説明と切り出して詳しく説明する。

10.2.4.2.1. MockMvcとは

Spring Testには、Spring MVC フレームワークと結合した状態でテストするための仕組みとして、 org.springframework.test.web.servlet.MockMvcクラスを提供している。 MockMvcを使用すると、アプリケーションサーバ上にデプロイすることなくSpring MVCの動作を再現できるため、 サーバやデータベースを用意する手間を省くことができる。 なお、Spring MVCの詳細についてはOverview of Spring MVC Processing Sequenceを参照されたい。

テスト実行時にリクエストを受けてから、レスポンスを返すまでの MockMvcの処理フローを、以下の図に示す。

../../_images/UsageOfLibraryForTestMockMvcProcessFlow.png
項番 説明
(1)
テストメソッドは、Spring Testが用意したorg.springframework.test.web.servlet.TestDispatcherServletにリクエストするデータをセットアップする。
(2)
MockMvcTestDispatcherServletに疑似的なリクエストを行なう。
(3)
TestDispatcherServletは、リクエスト内容に一致するControllerのメソッドを呼び出す。
(4)
テストメソッドは、MockMvcから実行結果を受け取り、実行結果の妥当性を検証する。

また、MockMvcには2つの動作オプションが実装されている。 テストを行う際は、それぞれの特性を把握し、用途毎に適したオプションを選択されたい。

以下に、2つのオプションの概要を示す。

動作オプション 概要
webAppContextSetup
spring-mvc.xmlなどで定義したSpring MVC の設定を読み込み、 WebApplicationContextを生成することで、デプロイ時とほぼ同じ状態でテストすることができる。
standaloneSetup
ControllerにDIされているコンポーネントを、テストで利用する設定ファイルに定義することで、 Spring Testが生成したDIコンテナを用いてテストを行うことができる。 よって、Spring MVC のフレームワーク機能を利用しつつ、Controllerのテストを単体テスト観点で行なうことができる。

以下に、2つのオプションのメリット、デメリットを示す。

動作オプション メリット デメリット
webAppContextSetup
実際の稼働で使用する設定ファイルを読み込むことで、 アプリケーションを動かさなければ確認できないこともデプロイなしで検証することができる。 実際の設定ファイルを読み込みテストするため、設定ファイルが正しく作成されているかを確認することもできる。
また、Bean定義にモッククラスを指定しておけば、ControllerにDIされるServiceなどをモック化することも可能である。
巨大なアプリケーションをテストする場合や、膨大なBean定義を読み込む場合は実行に時間がかかってしまう。 そのため、デプロイする場合の設定ファイルから、必要な記述だけを抽出した設定ファイルを用意するなどの工夫が必要となる。
standaloneSetup
生成されるDIコンテナに特定のInterceptorResolver等を適用してテストを実施できる。 そのため、Springの設定ファイルを参照せずコントローラ単体だけ見たい場合は、webAppContextSetupよりも実施コストが低い。
InterceptorResolverなどを多く適用するテストにおける設定コストが高い。
また、あくまでContorollerの単体テスト観点で動作するため、 Spring MVC のフレームワーク機能と合わせてControllerのテストを行いたい場合は、 webAppContextSetupでのテストを検討する必要があることに留意されたい。

10.2.4.2.2. MockMvcのセットアップ

ここではMockMvcの2つのオプションについて、 実際にテストで使用する際のセットアップ方法を説明する。

10.2.4.2.2.1. webAppContextSetupによるセットアップ

ここでは、webAppContextSetupでテストを行うためのセットアップ方法について説明する。

MockMvcのセットアップ設定例を以下に示す。

  • MockMvcのセットアップ設定例
@RunWith(SpringJUnit4ClassRunner.class)
@ContextHierarchy({ @ContextConfiguration({ // (1)
        "classpath:META-INF/spring/applicationContext.xml",
        "classpath:META-INF/spring/spring-security.xml" }),
        @ContextConfiguration("classpath:META-INF/spring/spring-mvc.xml") })
@WebAppConfiguration // (2)
public class MemberRegisterControllerWebAppContextTest {

    @Inject
    WebApplicationContext webApplicationContext; // (3)

    MockMvc mockMvc;

    @Before
    public void setUp() {

        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) // (4)
                .alwaysDo(log()).build();
    }

    @Test
    public void testRegisterConfirm01() throws Exception {

        ResultActions results = mockMvc.perform(post("/member/register")
                // omitted
                .param("confirm", "");

        results.andExpect(status().is(200));

        // omitted
    }
}
項番 説明
(1)
テスト実行時に生成するDIコンテナの設定ファイルを指定する。 DIコンテナの階層関係については、@org.springframework.test.context.ContextHierarchyを使うことで再現することができる。 DIコンテナの階層関係についてはアプリケーションコンテキストの構成とBean定義ファイルの関係を参照されたい。
(2)
Webアプリケーション向けのDIコンテナ(WebApplicationContext)が作成できるようになる。 また、@WebAppConfigurationを指定すると開発プロジェクト内のsrc/main/webappがWebアプリケーションのルートディレクトリ になるが、これはMavenの標準構成と同じなので特別に設定を加える必要はない。
(3)
テスト実行時に使用するDIコンテナをインジェクションする。
(4)
テスト実行時に使用するDIコンテナを指定して、MockMvcを生成する。

10.2.4.2.2.2. standaloneSetupによるセットアップ

ここでは、standaloneSetupでテストを行うためのセットアップ方法について説明する。

MockMvcのセットアップ設定例を以下に示す。

  • MockMvcのセットアップ設定例
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
        "classpath:META-INF/spring/applicationContext.xml",
        "classpath:META-INF/spring/test-context.xml",
        "classpath:META-INF/spring/spring-mvc-test.xml"})
public class MemberRegisterControllerStandaloneTest {

    @Inject
    MemberRegisterController target;

    MockMvc mockMvc;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.standaloneSetup(target).alwaysDo(log()).build(); // (1)
    }

    @Test
    public void testRegisterConfirm01() throws Exception {

        ResultActions results = mockMvc.perform(post("/member/register")
                // omitted
                .param("password", "testpassword")
                .param("reEnterPassword", "testpassword"));

        results.andExpect(status().is(200));

        // omitted
    }
}
項番 説明
(1)
テスト対象のControllerを指定して、MockMvcを生成する。 必要に応じてorg.springframework.test.web.servlet.setup.StandaloneMockMvcBuilderのメソッドを呼び出して、 Spring Testが生成するDIコンテナをカスタマイズすることができる。 カスタマイズするためのメソッドについての詳細は、 StandaloneMockMvcBuilderのJavadocを参照されたい。

10.2.4.2.3. MockMvcによるテストの実装

ここではMockMvcによるテスト実行の流れとして、リクエストデータの設定から、 リクエスト送信の実装方法、実行結果の検証、出力まで説明する。

10.2.4.2.3.1. リクエストデータの設定

リクエストデータの設定は、org.springframework.test.web.servlet.request.MockHttpServletRequestBuilderorg.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilderのファクトリメソッドを使用して行う。

ここでは、2つのクラスのファクトリメソッドの中から主要なメソッドについて紹介する。 詳細は、MockHttpServletRequestBuilder のJavadocまたはMockMultipartHttpServletRequestBuilder のJavadocを参照されたい。

MockHttpServletRequestBuilderの主なメソッド
メソッド名 説明
param/ params テスト実行時のリクエストに、リクエストパラメータを追加するメソッド。
content テスト実行時のリクエストに、リクエストボディを追加するメソッド。
header/ headers テスト実行時のリクエストに、リクエストヘッダーを追加するメソッド。 contentTypeacceptなどの特定のヘッダーを指定するためのメソッドも提供されている。
requestAttr リクエストスコープにオブジェクトを設定するメソッド。
flashAttr フラッシュスコープにオブジェクトを設定するメソッド。
sessionAttr セッションスコープにオブジェクトを設定するメソッド。
cookie テスト実行時のリクエストに、指定したcookieを追加するメソッド。

MockMultipartHttpServletRequestBuilderの主なメソッド
メソッド名 説明
file テスト実行時のリクエストに、アップロードするファイルを設定するメソッド。
part テスト実行時のリクエストに、 multipart/form-dataのPOSTリクエストで受信されたパーツまたはフォームアイテムを追加するメソッド。 このメソッドを利用することで、テスト実行時のリクエストにファイル以外のリクエストパラメータを追加することができる。

ここでは、paramメソッドを用いたリクエストデータの設定と、 postメソッドを用いたリクエスト実行の例を示す。

以下に、テスト対象のControllerの実装を示す。

  • テスト対象のControllerクラス
@Controller
@RequestMapping("member/register")
@TransactionTokenCheck("member/register")
public class MemberRegisterController {

    @RequestMapping(method = RequestMethod.POST, params = "confirm")
    public String registerConfirm(@Validated MemberRegisterForm memberRegisterForm,
        BindingResult result, Model model) {

        // omitted

        return "C1/memberRegisterConfirm";
    }
}

以下に、リクエスト送信の実装例を示す。

  • リクエスト送信の実装例
@Test
public void testRegisterConfirm01() throws Exception {
    mockMvc.perform(
         post("/member/register")
        .param("confirm", "")); // (1)
}
項番 説明
(1)
confirmをリクエストパラメータに持つリクエストデータを設定している。

10.2.4.2.3.2. リクエスト送信の実装

設定したリクエストデータをMockMvcperformメソッドの引数として渡すことで、 テストで利用するリクエストデータを設定し、DispatcherServletに疑似的なリクエストを行なう。 MockMvcRequestBuildersのメソッドには、getpostfileUploadといったメソッドが、リクエストの種類ごとに提供されている。 詳細は、MockMvcRequestBuilders のJavadocを参照されたい。

以下に、リクエスト送信の実装例を示す。

  • リクエスト送信の実装例
@Test
public void testRegisterConfirm01() throws Exception {
    mockMvc.perform( // (1)
         post("/member/register") // (2)
        .param("confirm", ""));
}
項番 説明
(1)
リクエストを実行し、返り値として実行結果の検証を行うためのResultActionsクラスを返す。 詳細は後述の実行結果検証の実装を参照されたい。
(2)
/member/registerへPOSTリクエストを実行するように設定している。

Warning

テスト時のトランザクショントークンチェック、CSRFチェック

テスト対象がトランザクショントークンチェックやCSRFチェックを利用している場合は、 mockMvcのリクエストについてもチェックが適用されることに注意されたい。 なお、本章ではspring-securityの設定は無効にしているため、CSRFチェックは行われていない。

Note

“/”から始まらないパスへリクエストを送信する際の挙動

MockMvcRequestBuildersの呼び出すUriComponentBuilderは仕様上、スキームまたはパスから始める必要がある。そのため、Spring Framework 5.2.3以前では”/”から始まらないパスへリクエストを送る場合404 Not Foundが発生していたが、 Spring Framework 5.2.4からはスキームが正しいことをアサートする処理が追加されたためアサーションエラーが発生するようになった。

10.2.4.2.3.3. 実行結果検証の実装

実行結果の検証には、org.springframework.test.web.servlet.ResultActionsandExpectメソッドを使用する。 andExpectメソッドの引数にはorg.springframework.test.web.servlet.ResultMatcherを指定する。 Spring Testは、org.springframework.test.web.servlet.result.MockMvcResultMatchersのファクトリメソッドを介して さまざまなResultMatcherを提供している。

ここでは、andExpectメソッドの引数として、主要となるMockMvcResultMatchersのメソッドを紹介する。 ここで紹介しないメソッドについては、 MockMvcResultMatchers のJavadocを参照されたい。

MockMvcResultMatchersの主なメソッド
メソッド名 説明
status HTTPステータスコードを検証するメソッド。
view Controllerが返却したView名を検証するメソッド。
model Spring MVCのModelについて検証するメソッド
request リクエストスコープおよびセッションスコープの状態、 Servlet 3.0からサポートされている非同期処理の処理状態を検証するメソッド。
flash フラッシュスコープの状態を検証するメソッド。
redirectedUrl リダイレクト先のパスを検証するメソッド。 redirectedUrlPatternメソッドを用いたパターンによる検証も提供されている。
fowardedUrl フォワード先のパスを検証するメソッド。 forwardedUrlPatternメソッドを用いたパターンによる検証も提供されている。
content レスポンスボディの中身を検証するメソッド。 jsonPathやxPathなどの特定のコンテンツ向けのメソッドも提供されている。
header レスポンスヘッダーの状態を検証するメソッド。
cookie cookieの状態を検証するメソッド。

以下に、テストの実行結果検証の実装例を示す。

  • 実行結果検証の実装例
@Test
public void testRegisterConfirm01() throws Exception {
    mockMvc.perform(post("/member/register")
        .param("confirm", ""));
        .andExpect(status().is(302)) // (1)
}
項番 説明
(1)
テスト実行時のリクエストデータを設定している。 andExpectメソッドはResultActionsからチェーンして記述することができるため、 IDEの補完機能によってコーディングの負担を減らすことができる。

Warning

Modelの検証とアサーションライブラリ

Spring TestではModelの検証として、modelメソッドにチェーンする形で org.springframework.test.web.servlet.result.ModelResultMatchersattributeメソッドを使用することが できる。このメソッドを用いることでModelの中身を検証することができるが、引数としてHamcrestの org.hamcrest.Matcherを使用するため、Hamcrest以外のアサーションライブラリを使用する場合は注意されたい。

Hamcrest以外のアサーションライブラリを併用する場合は、MvcResultからModelAndViewオブジェクトを取得し、 さらにModelAndViewオブジェクトからModelに格納されたオブジェクトを取得することで、使用している アサーションライブラリを使ってModelを検証することができる。

以下にModelAndViewオブジェクトから取得したModelの検証例を示す。

@Test
public void testRegisterConfirm01() throws Exception {
    MvcResult mvcResult = mockMvc.perform(post("/member/register").param("confirm", "")
                .param("kanjiFamilyName", "電電")
                .andExpect(status().is(200))
                .andReturn(); // (1)

    ModelAndView mav = mvcResult.getModelAndView(); // (2)

    MemberRegisterForm actForm = (MemberRegisterForm) mav.getModel().get("memberRegisterForm");

    assertThat(actForm.getKanjiFamilyName(), is("電電"));
    // omitted
}
項番 説明
(1)
ResultActionsandReturnメソッドを使用して MvcResultオブジェクトを取得する。
(2)
MvcResultからModelAndViewオブジェクトを取得し、ModelAndViewオブジェクトから Modelに格納されたオブジェクトを取得してModelの検証を行う。

10.2.4.2.3.4. 実行結果出力の実装

テスト実行時のログ出力などを有効化する場合は、ResultActionsalwaysDoメソッドやandDoメソッドを使う。 ログの出力などは共通処理になる場合が多いため、 MockMvc生成時にStandaloneMockMvcBuilderalwaysDoメソッドを使うことを推奨する。

alwaysDoメソッドの引数には、実行結果に対して任意の処理を行なうorg.springframework.test.web.servlet.ResultHandlerを指定する。Spring Testでは、org.springframework.test.web.servlet.result.MockMvcResultHandlersのファクトリメソッドを介して さまざまなResultHandlerを提供している。 ここでは、alwaysDoメソッドの引数として主要となるMockMvcResultHandlersのメソッドを紹介する。 各メソッドの詳細については、 MockMvcResultHandlers のJavadocを参照されたい。

MockMvcResultHandlersの主なメソッド
メソッド名 説明
log 実行結果をデバッグレベルでログ出力するメソッド。 ログ出力時に使用されるロガー名はorg.springframework.test.web.servlet.resultである。
print 実行結果を任意の出力先に出力するメソッド。出力先を指定しない場合、標準出力が出力先になる。

以下に、テストの実行結果出力の設定例を示す。

  • 実行結果出力の設定例
@Before
public void setUp() {
    mockMvc = MockMvcBuilders.standaloneSetup(target).alwaysDo(log()).build(); // (1)
}
項番 説明
(1)
alwayDoメソッドの引数にlogメソッドを指定することで、 mockMvcを用いたテスト実行の際は、常に実行結果をログとして出力する。

Note

テストケースごとの出力設定

テスト実行時のログ出力などをテストケースごとに有効化する場合は、ResultActionsandDoメソッドを使う。 andDoメソッドもalwaysDoメソッドと同じく引数にResultHandlerを指定する。

以下に、ログ出力をテストケースごとに有効化する場合の設定例を示す。

  • ログ出力を常に有効化する場合の設定例
@Test
public void testSearchForm() throws Exception {
    mockMvc.perform(get("/ticket/search").param("form", ""))
            .andDo(log()); // (1)
}
項番 説明
(1)
テストの実行結果をログ出力する。

10.2.4.3. Mockito

ここでは、モックの概要、Mockitoの使い方について説明する。

10.2.4.3.1. Mockitoとは

Mockitoとは、単体テストにおいてテスト対象の依存クラスをモック化する際に使われる モックライブラリの一つである。 モックライブラリを利用することで、モックの生成を簡単に行なうことができるため、 単体テストの実装時にはよく利用されている。

ここからは、モックについての説明を行なう。

10.2.4.3.1.1. モックの概要

モックとは、テスト対象が依存するクラスの代用となる疑似クラスである。 このように依存するクラスをモックに置き換えることをモック化と呼ぶ。 モックは単体テストにおいて、依存クラスが正しく使用されているかを検証するために使用される。 テスト対象のクラスのみ着目してテストを行いたい場合や、テスト対象の依存クラスが完成していないときは、 依存クラスをモック化してテストを行なうことを考えるべきである。

以下に、モックを利用したテストのフロー図を示す。

../../_images/UsageOfLibraryForTestMockCreationOfMockito.png
項番 説明
(1)
依存クラスが作成され、動作も保障されている場合は、 そのまま依存クラスを用いてテストすればよい。
(2)
依存クラスの動作が保障されていない場合や、作成されていない場合など、 依存クラスを利用できない場合は、モッククラスを用いてテストする。

実際に依存クラスをモック化する場合、通常テスト実施者自身でモッククラスを用意する必要がある。 しかし、検証のためのクラスをテストごとに一から作成していては、 テスト実施に多大なコストがかかると予想される。 そのような場合に利用されるのが、モック作成のためのモックライブラリである。 モックライブラリを用いることで、より簡単にモックを作成することができるため、 モックを利用するときはモックライブラリの使用を推奨する。

本章では、代表的なモックライブラリとしてMockitoを使用し説明を行なう。

10.2.4.3.2. Mockitoの利用

Mockitoは、依存クラスのモック化、メソッドの呼び出し検証、 メソッドの引数検証など、テストを行なう上で必要となる機能を提供している。 しかし、テスト対象のコードによってはMockitoを利用できない場合もあるので注意されたい。

ここでは、Mockitoを利用できない状況の中でも、 特に注意が必要となる状況について紹介する。

10.2.4.3.2.1. モック化の制限

Mockitoでは、final宣言、private宣言されたクラス/メソッド、 static宣言されたメソッドをモック化することができない。 通常のオブジェクト指向に沿った実装であれば、モック化に制限のかかるようなテストになることは考えにくいため、 このような事態に直面した場合はテスト対象の設計に問題がないか一度確認したほうがよい。

その他のMockitoの制限については、 What are the limitations of Mockitoを参照されたい。

10.2.4.3.3. Mockitoの機能

ここでは、Mockitoの代表的な機能として、モックの作成、 モック化されたメソッドの定義、検証について紹介する。

10.2.4.3.3.1. モックの生成

Mockitoのモック化には2種類の方法が存在する。 1つは、mockメソッドを用いて依存クラスをすべてモックにする方法、 もう1つは、spyメソッドを用いて依存クラスの一部のメソッドのみをモックにする方法である。

ここではより単純な、依存クラスをすべてモック化する方法について紹介する。 依存クラスの一部のみをモックにする方法については、 MockitoのJavadocを参照されたい。

完全にモック化する場合、基本的にはmockメソッドを用いてモック化する。

以下に、mockメソッドを用いたモック化の例を示す。

public class TicketReserveServiceImpl implements TicketReserveService {

    @Inject // (1)
    ReservationRepository reservationRepository;

    // omitted
}
項番 説明
(1)
テスト対象のTicketReserveServiceImplReservationRepositoryをインジェクトしているため、 ReservationRepositoryに依存した実装となっている。
public class TicketReserveServiceImplMockTest {

    ReservationRepository mockReservationRepository = mock(ReservationRepository.class); // (1)

    private TicketReserveServiceImpl target;

    @Test
    public void testRegisterReservation() {

        target.reservationRepository = mockReservationRepository; // (2)

        // omitted
    }
}
項番 説明
(1)
mockメソッドを使うことで、TicketReserveServiceImplが依存していた ReservationRepositoryをモック化している。
(2)
テスト対象のフィールドにモッククラスのオブジェクトを適用する。

mockメソッドを使用すると、テスト実施者自身が1つずつモックを適用していく必要があった。 そのため、テスト対象の依存クラスが数多く存在する場合は、記述量も増加し実装コストもかかる。 そのような場合は、@Mockアノテーションを使用したモックを自動で適用させる方法を推奨する。 また、本章ではモック化の際に、mockメソッドより簡潔に記述できる@Mockアノテーションを使用している。

以下に、@Mockアノテーションを用いたモック化の例を示す。

  • TicketReserveServiceImplMockTest.java
public class TicketReserveServiceImplMockTest {

    @Mock // (1)
    ReservationRepository mockReservationRepository;

    @InjectMocks // (2)
    private TicketReserveServiceImpl target;

    @Rule // (3)
    public MockitoRule mockito = MockitoJUnit.rule();

    // omitted
}
項番 説明
(1)
@Mockアノテーションをモック化したいクラスに付与することで、対象クラスのモックオブジェクトが Mockitoによって自動的に代入される。モッククラスを別途定義する必要はない。
(2)
@InjectMocksアノテーションをテスト対象としたい具象クラスに付与することで、対象クラスのインスタンスが Mockitoによって自動的に代入され、さらに対象クラスが依存しているクラスと、 @Mockアノテーションが付与されたクラスが一致する場合、自動的にモックオブジェクトが設定される。
(3)
JUnitでMockitoを利用するための宣言。 @Ruleにより、後述のアノテーションベースのモックオブジェクトの初期化機能が利用可能になる。

10.2.4.3.3.2. メソッドのモック化

モック化したオブジェクトの持つすべてのメソッドは、 返り値がプリミティブ型の場合はそれぞれの型の初期値(例: int型の場合は0)を、 それ以外の場合はnullを返すようなメソッドとして定義される。 そのため、テストを行なう際は実施するテスト内容に合わせて、 メソッドの返り値を改めて定義する必要がある。

10.2.4.3.3.2.1. 引数、返り値の設定

メソッドのモック化には、Mockitoクラスのwhenメソッドと、 whenメソッドが返す org.mockito.stubbing.OngoingStubbingインスタンスのメソッドを使用する。 whenメソッドの引数にはモック化するメソッドとその引数を指定し、実行時の返り値をOngoingStubbingのメソッドで定義する。

以下に、OngoingStubbingの主なメソッドを示す。 OngoingStubbingの詳細については、 OngoingStubbingのJavadocを、 またwhenメソッドについては MockitoのJavadocを参照されたい。

OngoingStubbingの主なメソッド
メソッド名 説明
thenReturn メソッドが呼び出されるときの返り値を引数に設定するメソッド。
thenThrow メソッドが呼び出されるときにthrowされるThrowableオブジェクトを引数に設定するメソッド。 引数にはthrowしたい例外クラスのjava.lang.Classオブジェクトを指定することもできる。

以下に、thenReturnの使用例を示す。

public class TicketReserveServiceImplMockTest {

    @Test
    public void testRegisterReservation() {

        Reservation testReservation = new Reservation();
        reservation.setReserveNo("0000000001");

        when(reservationRepository.insert(testReservation)).thenReturn(1); // (2)
    }
}
項番 説明
(1)
whenメソッドの引数には、動作を定義したいメソッドとその引数を指定する。
(2)
insertメソッドの引数にtestReservationを指定することで、 テスト対象がinsertメソッドを引数testReservationで実行するとき、 返り値は”1” になる。
10.2.4.3.3.2.2. 任意の引数による定義

モック化したいメソッドの引数にorg.mockito.ArgumentMatchersのメソッドを用いることで、 任意の引数を対象に返り値を定義することもできる。

以下に、ArgumentMatchersの主なメソッドを示す。 詳細については、ArgumentMatchersのJavadocを参照されたい。

ArgumentMatchersの主なメソッド
メソッド名 説明
any モック化されるメソッドの引数が任意の型のObjectであることを示すメソッド。
anyString モック化されるメソッドの引数がnull以外の任意のStringであることを示すメソッド。
anyInt モック化されるメソッドの引数が任意のint型、またはnull以外の任意のIntegerであることを示すメソッド。

以下に、anyの使用例を示す。

public class TicketReserveServiceImplMockTest {

    @Test
    public void testRegisterReservation() {

        // omitted

        when(reservationRepository.insert(any(Reservation.class))).thenReturn(0); // (1)
    }
}
項番 説明
(1)
insertメソッドの引数として anyメソッドでReservationクラスを指定することで、 insertメソッドを任意のReservation引数で実行するとき、 返り値が”0” になるように設定している。

10.2.4.3.3.3. 返り値がvoid型であるメソッドのモック化

モック化された返り値がvoid型であるメソッドは、デフォルトでは何も動作しないメソッドとして定義される。 そのため、例外をスローさせたい場合などは改めて定義する必要がある。

10.2.4.3.3.3.1. 動作の設定

whenメソッドでは定義できない返り値がvoid型であるメソッドについては、 MockitoクラスのdoThrowメソッドなどを用いることで定義できる。

以下に、返り値がvoid型であるメソッドを再定義するためのMockitoの主なメソッドを示す。 詳細については、 MockitoのJavadocを参照されたい。

返り値がvoid型であるメソッドを再定義するMockitoの主なメソッド
メソッド名 説明
doThrow 返り値がvoid型であるメソッドにthrowさせる例外を設定する場合に用いるメソッド。
doNothing 返り値がvoid型であるメソッドに何もさせないよう設定する場合に用いるメソッド。

以下に、doThrowの使用例を示す。

doThrow(new RuntimeException()).when(mock).someVoidMethod(); // (1)
項番 説明
(1)
doThrowメソッドはwhenメソッドの前に記述し、 whenメソッドはその後、チェーンする形で記載する。
doThrowメソッドの引数にスローしたい例外を指定することで、 モック化したメソッドが実行されるときに例外をスローするようになる。

10.2.4.3.3.4. モック化したメソッドの検証

Mockitoで作成したオブジェクトをモックとして用いる場合は、 Mockitoクラスのverifyメソッドを用いることで、 モック化したメソッドの呼び出しについて検証することができる。

verifyメソッドは引数にモックを指定し、チェーンする形で モック化したメソッドを続けることで、そのメソッドが正しく呼ばれているかどうかを検証できる。 また、verifyメソッドの引数としてモックとorg.mockito.verification.VerificationModeを指定することで、より詳しくメソッドの呼び出しについて検証できる。

以下に、VerificationModeの主なメソッドを示す。 詳細については、VerificationModeのJavadocを参照されたい。

VerificationModeの主なメソッド
メソッド名 説明
times 期待する呼び出し回数を設定するメソッド。 引数に期待する呼び出し回数を設定できる。 verifyメソッドの引数にVerificationModeを指定しない場合は times(1)が設定される。
never 呼び出されていないことを期待する場合に設定するメソッド。

以下に、timesの使用例を示す。

@Test
public void testRegisterReservation() {

    // omitted

    when(reservationRepository.insert(testReservation)).thenReturn(1);

    TicketReserveDto ticketReserveDto = target.registerReservation(testReservation); // (1)

    verify(reservationRepository, times(1)).insert(testReservation); // (2)
}
項番 説明
(1)
targetinsertメソッドでは、 ReservationRepositoryinsertメソッドが1回実行されるような実装になっている。
(2)
verifyメソッドの引数にモックオブジェクトと、 timesメソッドを指定することで、insertメソッドが引数testReservationで 正しく1回呼ばれているかを検証することができる。
この場合は、timesメソッドの引数が”1” なので省略しても同様の検証となる。