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を使用して設定ファイルを読み込む例を示す。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
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestContextConfig.class, SampleEnvConfig.class, SampleInfraConfig.class }) // (1)
@Transactional
public class MemberRepositoryTest {
@Inject
MemberRepository target; // (2)
}
項番 |
説明 |
|---|---|
(1)
|
@ContextConfigurationにSampleInfraConfig.classを指定する。 |
(2)
|
SampleInfraConfig.javaでBean登録されているMemberRepositoryをインジェクションする。 |
@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 |
|
DirtiesContextBeforeModesTestExecutionListener |
テストで使用するDIコンテナのライフサイクル管理機能を提供している。 テストクラスまたはテストメソッドの実行前に呼び出される。 |
DependencyInjectionTestExecutionListener |
テストで使用するインスタンスへのDI機能を提供している。 |
DirtiesContextTestExecutionListener |
テストで使用するDIコンテナのライフサイクル管理機能を提供している。 テストクラスまたはテストメソッドの実行後に呼び出される。 |
TransactionalTestExecutionListener |
テスト実行時のトランザクション管理機能を提供している。 |
SqlScriptsTestExecutionListener |
|
各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
|
SpringMvcConfig.javaや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(classes = { ApplicationContextConfig.class, SpringSecurityConfig.class }),
@ContextConfiguration(classes = { SpringMvcConfig.class }) }) // (1)
@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
}
}
@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(classes = { ApplicationContextConfig.class, TestContextConfig.class, SpringMvcTestConfig.class })
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
}
}
@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のファクトリメソッドを使用して行う。
メソッド名 |
説明 |
|---|---|
|
テスト実行時のリクエストに、リクエストパラメータを追加するメソッド。 |
|
テスト実行時のリクエストに、リクエストボディを追加するメソッド。 |
|
テスト実行時のリクエストに、リクエストヘッダーを追加するメソッド。
|
|
リクエストスコープにオブジェクトを設定するメソッド。 |
|
フラッシュスコープにオブジェクトを設定するメソッド。 |
|
セッションスコープにオブジェクトを設定するメソッド。 |
|
テスト実行時のリクエストに、指定したcookieを追加するメソッド。 |
メソッド名 |
説明 |
|---|---|
|
テスト実行時のリクエストに、アップロードするファイルを設定するメソッド。 |
|
テスト実行時のリクエストに、
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のメソッドを紹介する。メソッド名 |
説明 |
|---|---|
|
HTTPステータスコードを検証するメソッド。 |
|
|
|
Spring MVCの |
|
リクエストスコープおよびセッションスコープの状態、 Servlet 3.0からサポートされている非同期処理の処理状態を検証するメソッド。 |
|
フラッシュスコープの状態を検証するメソッド。 |
|
リダイレクト先のパスを検証するメソッド。
|
|
フォワード先のパスを検証するメソッド。
|
|
レスポンスボディの中身を検証するメソッド。 jsonPathやxPathなどの特定のコンテンツ向けのメソッドも提供されている。 |
|
レスポンスヘッダーの状態を検証するメソッド。 |
|
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のメソッドを紹介する。メソッド名 |
説明 |
|---|---|
|
実行結果をデバッグレベルでログ出力するメソッド。
ログ出力時に使用されるロガー名は |
|
実行結果を任意の出力先に出力するメソッド。出力先を指定しない場合、標準出力が出力先になる。 |
以下に、テストの実行結果出力の設定例を示す。
実行結果出力の設定例
@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の主なメソッドを示す。メソッド名 |
説明 |
|---|---|
|
メソッドが呼び出されるときの返り値を引数に設定するメソッド。 |
|
メソッドが呼び出されるときにthrowされる |
以下に、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の主なメソッドを示す。メソッド名 |
説明 |
|---|---|
|
モック化されるメソッドの引数が任意の型の |
|
モック化されるメソッドの引数が |
|
モック化されるメソッドの引数が任意のint型、または |
以下に、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の使用例を示す。
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の主なメソッドを示す。メソッド名 |
説明 |
|---|---|
|
期待する呼び出し回数を設定するメソッド。引数に期待する呼び出し回数を設定できる。
verifyメソッドの引数にVerificationModeを指定しない場合はtimes(1)が設定される。 |
|
呼び出されていないことを期待する場合に設定するメソッド。 |
以下に、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“なので省略しても同様の検証となる。 |