10.2.4. 単体テストで利用するOSSライブラリの使い方¶
本節では、単体テストで利用するOSSライブラリとして、Spring Test(MockMvc)、Mockitoについて説明する。
10.2.4.1. Spring Test¶
10.2.4.1.1. Spring Test とは¶
Spring Testでは主に以下の機能が提供されている。
- テスティングフレームワーク(JUnit)上でSpringのDIコンテナを動かす機能
- テストデータをセットアップする機能
- アプリケーションサーバ上にデプロイせずに、Spring MVCの動作を再現する機能
- テストに最適なトランザクション管理機能
その他、様々なSpring固有のアノテーションや、単体テストで利用するAPIが提供されている。
Spring Testは、テスティングフレームワーク上で動作するテスト用のフレームワーク機能として、Spring TestContext Frameworkを提供している。
Spring TestContext Frameworkの処理フロー図を以下に示す。
項番 | 説明 |
---|---|
(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 の処理を呼び出す。
トランザクションの管理やテストデータのセットアップ処理は、
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.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="typeAliasesPackage" value="com.example.domain.model, com.example.domain.repository" />
</bean>
<!-- scan for Mappers -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.domain.repository" />
</bean>
</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)
|
@ContextConfiguration にsample-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コンテナのライフサイクル管理機能を提供している。 テストクラスまたはテストメソッドの実行前に呼び出される。 |
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 は内部でTransactionalTestExecutionListener とcom.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とは¶
org.springframework.test.web.servlet.MockMvc
クラスを提供している。MockMvc
を使用すると、アプリケーションサーバ上にデプロイすることなくSpring MVCの動作を再現できるため、サーバやデータベースを用意する手間を省くことができる。テスト実行時にリクエストを受けてから、レスポンスを返すまでのMockMvc
の処理フローを、以下の図に示す。
項番 | 説明 |
---|---|
(1)
|
テストメソッドは、Spring Testが用意した
org.springframework.test.web.servlet.TestDispatcherServlet にリクエストするデータをセットアップする。 |
(2)
|
MockMvc はTestDispatcherServlet に疑似的なリクエストを行なう。 |
(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コンテナに特定の
Interceptor やResolver 等を適用してテストを実施できる。そのため、Springの設定ファイルを参照せずコントローラ単体だけ見たい場合は、
webAppContextSetup よりも実施コストが低い。 |
Interceptor やResolver などを多く適用するテストにおける設定コストが高い。また、あくまで
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().isOk());
// 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().isOk());
// 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.MockHttpServletRequestBuilder
やorg.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder
のファクトリメソッドを使用して行う。
メソッド名 | 説明 |
---|---|
param / params |
テスト実行時のリクエストに、リクエストパラメータを追加するメソッド。 |
content |
テスト実行時のリクエストに、リクエストボディを追加するメソッド。 |
header / headers |
テスト実行時のリクエストに、リクエストヘッダーを追加するメソッド。
contentType やaccept などの特定のヘッダーを指定するためのメソッドも提供されている。 |
requestAttr |
リクエストスコープにオブジェクトを設定するメソッド。 |
flashAttr |
フラッシュスコープにオブジェクトを設定するメソッド。 |
sessionAttr |
セッションスコープにオブジェクトを設定するメソッド。 |
cookie |
テスト実行時のリクエストに、指定したcookieを追加するメソッド。 |
メソッド名 | 説明 |
---|---|
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. リクエスト送信の実装¶
MockMvc
のperform
メソッドの引数として渡すことで、テストで利用するリクエストデータを設定し、DispatcherServlet
に疑似的なリクエストを行なう。MockMvcRequestBuilders
のメソッドには、get
、post
、fileUpload
といったメソッドが、リクエストの種類ごとに提供されている。以下に、リクエスト送信の実装例を示す。
- リクエスト送信の実装例
@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.ResultActions
のandExpect
メソッドを使用する。andExpect
メソッドの引数にはorg.springframework.test.web.servlet.ResultMatcher
を指定する。org.springframework.test.web.servlet.result.MockMvcResultMatchers
のファクトリメソッドを介してさまざまなResultMatcher
を提供している。andExpect
メソッドの引数として、主要となる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().isFound()) // (1)
}
項番 | 説明 |
---|---|
(1)
|
テスト実行時のリクエストデータを設定している。
andExpect メソッドはResultActions からチェーンして記述することができるため、IDEの補完機能によってコーディングの負担を減らすことができる。 |
Warning
Modelの検証とアサーションライブラリ
Spring TestではModel
の検証として、model
メソッドにチェーンする形でorg.springframework.test.web.servlet.result.ModelResultMatchers
のattribute
メソッドを使用することができる。このメソッドを用いることで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().isOk()) .andReturn(); // (1) ModelAndView mav = mvcResult.getModelAndView(); // (2) MemberRegisterForm actForm = (MemberRegisterForm) mav.getModel().get("memberRegisterForm"); assertThat(actForm.getKanjiFamilyName(), is("電電")); // omitted }
項番 説明 (1)ResultActions
のandReturn
メソッドを使用してMvcResult
オブジェクトを取得する。 (2)MvcResult
からModelAndView
オブジェクトを取得し、ModelAndView
オブジェクトからModel
に格納されたオブジェクトを取得してModel
の検証を行う。
10.2.4.2.3.4. 実行結果出力の実装¶
ResultActions
のalwaysDo
メソッドやandDo
メソッドを使う。MockMvc
生成時にStandaloneMockMvcBuilder
のalwaysDo
メソッドを使うことを推奨する。alwaysDo
メソッドの引数には、実行結果に対して任意の処理を行なうorg.springframework.test.web.servlet.ResultHandler
を指定する。org.springframework.test.web.servlet.result.MockMvcResultHandlers
のファクトリメソッドを介してさまざまなResultHandler
を提供している。alwaysDo
メソッドの引数として主要となる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
テストケースごとの出力設定
テスト実行時のログ出力などをテストケースごとに有効化する場合は、ResultActions
のandDo
メソッドを使う。
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とは¶
ここからは、モックについての説明を行なう。
10.2.4.3.1.1. モックの概要¶
以下に、モックを利用したテストのフロー図を示す。
項番 | 説明 |
---|---|
(1)
|
依存クラスが作成され、動作も保障されている場合は、
そのまま依存クラスを用いてテストすればよい。
|
(2)
|
依存クラスの動作が保障されていない場合や、作成されていない場合など、依存クラスを利用できない場合は、モッククラスを用いてテストする。
|
本章では、代表的なモックライブラリとしてMockitoを使用し説明を行なう。
10.2.4.3.2. Mockitoの利用¶
Mockitoは、依存クラスのモック化、メソッドの呼び出し検証、メソッドの引数検証など、テストを行なう上で必要となる機能を提供している。しかし、テスト対象のコードによってはMockitoを利用できない場合もあるので注意されたい。
ここでは、Mockitoを利用できない状況の中でも、特に注意が必要となる状況について紹介する。
10.2.4.3.2.1. モック化の制限¶
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
メソッドを用いて依存クラスの一部のメソッドのみをモックにする
完全にモック化する場合、基本的にはmock
メソッドを用いてモック化する。
以下に、mock
メソッドを用いたモック化の例を示す。
public class TicketReserveServiceImpl implements TicketReserveService {
@Inject // (1)
ReservationRepository reservationRepository;
// omitted
}
項番 | 説明 |
---|---|
(1)
|
テスト対象の
TicketReserveServiceImpl はReservationRepository をインジェクトしているため、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
の主なメソッドを示す。メソッド名 | 説明 |
---|---|
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); // (1)
}
}
項番 | 説明 |
---|---|
(1)
|
when メソッドの引数には、動作を定義したいメソッドとその引数を指定する。insert メソッドの引数にtestReservation を指定することで、テスト対象がinsert メソッドを引数testReservation で実行するとき、返り値は”1 ” になる。 |
10.2.4.3.3.2.2. 任意の引数による定義¶
モック化したいメソッドの引数にorg.mockito.ArgumentMatchers
のメソッドを用いることで、任意の引数を対象に返り値を定義することもできる。
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
の主なメソッドを示す。メソッド名 | 説明 |
---|---|
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
の主なメソッドを示す。メソッド名 | 説明 |
---|---|
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)
|
target のinsert メソッドでは、ReservationRepository のinsert メソッドが1回実行されるような実装になっている。 |
(2)
|
verify メソッドの引数にモックオブジェクトと、times メソッドを指定することで、insert メソッドが引数testReservation で正しく1回呼ばれているかを検証することができる。この場合は、
times メソッドの引数が”1 ” なので省略しても同様の検証となる。 |