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

本節では、単体テストで利用する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.junit.jupiter.SpringExtensionクラスが呼び出される。
(2)
SpringExtensionクラスは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の処理を呼び出す。
  • テストケースの全テストメソッド実行前(@BeforeAll

  • テストインスタンスの生成後

  • 各テストメソッドの実行前(@BeforeEach

  • 各テストメソッドの実行後(@AfterEach

  • テストケースの全テストメソッド実行後(@AfterAll

トランザクションの管理やテストデータのセットアップ処理は、TestExecutionListenerの処理によって行われる。
TestExecutionListenerの登録についてはTestExecutionListenerの登録を参照されたい。

10.2.4.1.1.1. Spring TestのDI機能

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

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

/**
 * Bean definitions for infrastructure layer.
 */
@Configuration
@MapperScan("com.example.domain.repository")
public class SampleInfraConfig {

    /**
     * MyBatisがマッパーを自動スキャンするパッケージを設定
     * Configure {@link MapperScannerConfigurer} bean.
     * @return Bean of configured {@link MapperScannerConfigurer}
     */
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer bean = new MapperScannerConfigurer();
        bean.setBasePackage("com.example.domain.repository");
        return bean;
    }

    /**
     * MyBatis設定
     * Configure {@link SqlSessionFactoryBean} bean.
     * @param dataSource Bean defined by SampleEnvConfig#dataSource
     * @see com.example.config.app.SampleEnvConfig#dataSource()
     * @return Bean of configured {@link SqlSessionFactoryBean}
     */
    @Bean("sqlSessionFactory")
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setTypeAliasesPackage("com.example.domain.model, com.example.domain.repository");
        bean.setConfiguration(MybatisConfig.configuration());
        return bean;
    }

}
  • MemberRepositoryTest.java

@ExtendWith(SpringExtension.class)
@ContextConfiguration(
        classes = { TestContextConfig.class, SampleEnvConfig.class, SampleInfraConfig.class }) // (1)
@Transactional
public class MemberRepositoryTest {

    @Inject
    MemberRepository target; // (2)
}

項番

説明

(1)
@ContextConfigurationSampleInfraConfig.classを指定する。
(2)
SampleInfraConfig.javaでBean登録されているMemberRepositoryをインジェクションする。
@ExtendWith(SpringExtension.class)@ContextConfiguration@SpringJUnitConfigアノテーションにまとめることもできる。
ただし、設定ファイルを@ContextConfigurationではなく@ContextHierarchyで指定する場合は、@SpringJUnitConfigアノテーションを使用できない。
@ContextHierarchyを使用する場合の例は、webAppContextSetupによるセットアップを参照されたい。
本ガイドラインでは@ContextHierarchyを使用しない限りは、@SpringJUnitConfigを使用する。
  • MemberRepositoryTest.java

@SpringJUnitConfig(classes = { TestContextConfig.class, SampleEnvConfig.class, SampleInfraConfig.class }) // (1)
@Transactional
public class MemberRepositoryTest {

    @Inject
    MemberRepository target;
}

項番

説明

(1)
@ContextConfigurationと同様に、@SpringJUnitConfigSampleInfraConfig.classを指定する。

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コンテナのライフサイクル管理機能を提供している。 テストクラスまたはテストメソッドの実行前に呼び出される。

DependencyInjectionTestExecutionListener

テストで使用するインスタンスへの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
SpringMvcConfig.javaspring-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. MockMvcTesterとは

org.springframework.test.web.servlet.assertj.MockMvcTesterorg.springframework.test.web.servlet.MockMvcをラップしたクラスで、AssertJサポートのエントリポイントを提供する。
本ガイドラインではAssertJの使用を想定しているため、MockMvcの代わりにMockMvcTesterを使用する。

10.2.4.2.3. MockMvcTesterのセットアップ

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

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

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

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

  • MockMvcTesterのセットアップ設定例

@ExtendWith(SpringExtension.class)
@ContextHierarchy({ @ContextConfiguration(classes = { ApplicationContextConfig.class, SpringSecurityConfig.class }),
      @ContextConfiguration(classes = { SpringMvcConfig.class }) }) // (1)
@WebAppConfiguration // (2)
public class MemberRegisterControllerWebAppContextTest {

    @Inject
    WebApplicationContext webApplicationContext; // (3)

    MockMvcTester mockMvc;

    @BeforeEach
    public void setUp() {

        mockMvc = MockMvcTester.from(webApplicationContext,
                builder -> builder.alwaysDo(log()).build());// (4)
    }

    @Test
    public void testRegisterConfirm01() throws Exception {

        MvcTestResult mvcTestResult = mockMvc.post().uri("/member/register")
                // omitted
                .param("confirm", "")
                .exchange();

        assertThat(mvcTestResult).hasStatusOk();

        // omitted
    }
}

項番

説明

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

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

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

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

  • MockMvcTesterのセットアップ設定例

@SpringJUnitConfig(classes = {ApplicationContextConfig.class, TestContextConfig.class,
        SpringMvcTestConfig.class})
public class MemberRegisterControllerStandaloneTest {

    @Inject
    MemberRegisterController target;

    MockMvcTester mockMvc;

    @BeforeEach
    public void setUp() {

        mockMvc = MockMvcTester.of(Collections.singletonList(target),
                builder -> builder.alwaysDo(log()).build()
        ); // (1)
    }

    @Test
    public void testRegisterConfirm01() throws Exception {

        MvcTestResult mvcTestResult = mockMvc.post().uri("/member/register")
                // omitted
                .param("password", "testpassword")
                .param("reEnterPassword", "testpassword")
                .exchange();

        assertThat(mvcTestResult).hasStatusOk();

        // omitted
    }
}

項番

説明

(1)
第1引数にはテスト対象のControllerのリストを指定する。
第2引数には必要に応じて、Spring Testが生成するDIコンテナをカスタマイズする関数を指定する。この関数はorg.springframework.test.web.servlet.setup.StandaloneMockMvcBuilderに基づいている。
カスタマイズするためのメソッドについての詳細は、StandaloneMockMvcBuilderのJavadocを参照されたい。

10.2.4.2.4. MockMvcTesterによるテストの実装

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


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

ここでは、MockMvcTesterを使用してリクエストを送信する方法について説明する。

最初に使用したいHTTPメソッドに応じたMockMvcTesterのメソッドを呼び出す。
その後org.springframework.test.web.servlet.assertj.MockMvcTester.MockMvcRequestBuilderのメソッドをチェーンしてリクエストデータの設定をする。
最後にexchangeメソッドを呼び出し、MvcTestResultクラスを受け取りAssertJで実行結果を検証できるようにする。
詳細は、MockMvcTeseter のJavadocおよびMockMvcRequestBuilder のJavadocを参照されたい。

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

以下に、テスト対象の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 {
    MvcTestResult mvcTestResult = mockMvc.post().uri("/member/register") // (1)
            .param("confirm", "") // (2)
            .exchange(); // (3)
}

項番

説明

(1)
/member/registerへPOSTリクエストを実行するように設定している。
(2)
confirmをリクエストパラメータに持つリクエストデータを設定している。
(3)
リクエストを実行し、返り値として実行結果の検証を行うためのMvcTestResultクラスを返す。

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.4.2. 実行結果検証の実装

実行結果の検証には、org.springframework.test.web.servlet.assertj.MvcTestResultAssertで提供されるAssertJのアサーションを使用する。

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

  • 実行結果検証の実装例

@Test
public void testRegisterConfirm01() throws Exception {
    // omitted

    assertThat(mvcTestResult).hasStatus(HttpStatus.FOUND); // (1)
}

項番

説明

(1)
リクエスト実行で取得したMvcTestResultクラスをassertThatメソッドに渡し、hasStatusメソッドでHTTPステータスコードが302 Foundであることを検証している。

10.2.4.2.4.3. 実行結果出力の実装

テスト実行時のログ出力などを有効化する場合は、MockMvcTester生成時にofメソッドもしくはfromメソッドの第2引数で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

実行結果を任意の出力先に出力するメソッド。出力先を指定しない場合、標準出力が出力先になる。

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

  • 実行結果出力の設定例

import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.log;

    @BeforeEach
    public void setUp() {
        mockMvc = MockMvcTester.of(Collections.singletonList(target),
                builder -> builder.alwaysDo(log()).build() // (1)
        );
    }

項番

説明

(1)
alwayDoメソッドの引数にlogメソッドを指定することで、mockMvcを用いたテスト実行の際は、常に実行結果をログとして出力する。

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種類の方法が存在する。

  • mockメソッドを用いて依存クラスをすべてモックにする

  • 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

@ExtendWith(MockitoExtension.class) // (1)
public class TicketReserveServiceImplMockTest {

    @Mock // (2)
    ReservationRepository mockReservationRepository;

    @InjectMocks // (3)
    private TicketReserveServiceImpl target;

    // omitted
}

項番

説明

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

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の使用例を示す。

@ExtendWith(MockitoExtension.class)
public class TicketReserveServiceImplMockTest {

    @Test
    public void testRegisterReservation() {

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

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

項番

説明

(1)
whenメソッドの引数には、動作を定義したいメソッドとその引数を指定する。
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の使用例を示す。

@ExtendWith(MockitoExtension.class)
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“なので省略しても同様の検証となる。