SpringBoot/テスト
の編集
https://cs27.org/wiki/kobespiral2021/?SpringBoot/%E3%83%86%E3%82%B9%E3%83%88
[
トップ
] [
編集
|
差分
|
履歴
|
添付
|
リロード
] [
新規
|
一覧
|
検索
|
最終更新
|
ヘルプ
]
-- 雛形とするページ --
(no template pages)
[[SpringBoot]] - 4/5中田 * Spring Boot Test [#i322aa14] Spring Bootでのテストの仕方をまとめ%%たかった%% #contents ** はじめに [#ad97d481] 公式Docを読む時間がある人は[[この項目:https://docs.spring.io/spring-boot/docs/2.4.3/reference/html/spring-boot-features.html#boot-features-testing]]を自分で読むのが一番です。 テストについて学ぶには、Spring Framework、Spring Bootについてのややディープな理解が必須なので、不安な人は[[中田/Techメモ/SpringBoot]]等を参照してください。 PitCoinで実際にテストを書いてみたので、[[こちら:http://teamserv.cs.kobe-u.ac.jp:443/tnakata/pitcointest]]も参考になると思います。 ** Spring Bootテスト全体の話 [#yb02716f] *** テストで使用する技術 [#me700c2d] Spring Bootでテストを行うためには、様々な知識が必要です。 Spring Frameworkのテスト = (バニラJava + 頻出ライブラリ) + (テスト + アサーション) + (DI + モッキング) + Auto-configuration - バニラJava -- 標準のJavaの書き方です。 -- (例:OOP、アノテーション、ラムダ式、JPA) - 頻出ライブラリ -- Javaでよく使われるライブラリです。 -- (例:Jackson) - テスト -- テストの本体です。 -- (例:JUnit5、JUnit4) - アサーション -- テストで満たすべき条件を記述します。 -- (例:AssertJ) - DI -- Bean注入です。 -- (例:@Bean、@Autowired、コンポーネントスキャン) - モッキング -- 注入するBeanをモック(はりぼてクラス)にすることで、クラスを切り離してテストができます。これがDIの主目的です。 -- (例:Mockito) - Auto-configuration -- Springの主機能で、依存性を記述するだけで指定した範囲の機能が有効化され、Beanが構成されます。 *** Bootテストの特徴 [#kc0bd9fb] Spring Bootのテストは、Spring Frameworkのテストをまとめたうえで指向性を持たせたものです。 build.gradleに testImplementation 'org.springframework.boot:spring-boot-starter-test' という依存を追加することで、Spring Frameworkのテストライブラリに加え、以下のような外部ライブラリがいくつかimportして利用できるようになります。 - JUnit 5 : テストライブラリ - AssertJ : アサーションライブラリ - Mockito : モッキングフレームワーク テストの種類に応じてアノテーションを変更することで、コンポーネントスキャンとAuto-configurationの範囲を指定(スライス)することができます。(@DataJpaTest、@WebMvcTestなど) Auto-configurationの内容は、[[@SpringBootTest:https://docs.spring.io/spring-boot/docs/2.4.3/reference/html/appendix-auto-configuration-classes.html#auto-configuration-classes]]、[[スライス:https://docs.spring.io/spring-boot/docs/2.4.3/reference/html/appendix-test-auto-configuration.html]]を参照してください。 @...Testのアノテーションは、 JUnit5の@ExtendWith(SpringExtension.class)を含み、Spring拡張機能が利用可能になります。JUnit4を利用する場合は@RunWith(...)を追加してください。 ** 具体的なテスト手法 [#yb49ccc2] *** Repository単体テスト(Spring Data JPA) [#y6b75efb] [[公式Doc:https://docs.spring.io/spring-boot/docs/2.4.3/reference/html/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-autoconfigured-jpa-test]]を参照。 //公式Docより @DataJpaTest class ExampleRepositoryTests { @Autowired private TestEntityManager entityManager; @Autowired private UserRepository repository; @Test void testExample() throws Exception { this.entityManager.persist(new User("sboot", "1234")); User user = this.repository.findByUsername("sboot"); assertThat(user.getUsername()).isEqualTo("sboot"); assertThat(user.getVin()).isEqualTo("1234"); } } Repository単体テストでは、インメモリのDBを利用します。 そのため、テスト用に依存を追加しておく必要があります。(例:H2を利用する場合は、testImplementation 'com.h2database:h2' をbuild.gradleに追記) @DataJpaTestは、@RepositoryをスキャンしてBeanを自動構成するため、RepositoryのAutowiredが可能です。その他のコンポーネント(@Serviceなど)はデフォルトではスキャンされません。 また、TestEntityManager(標準JavaのJPAにおけるEntityManagerのSpring Boot Testラップ版)のBeanも利用可能なため、Repositoryの機能を使わずにデータベースの初期化が可能になります(perisist()、clear()など)。 @DataJpaTestは@Transactionalを含むアノテーションであるため、テストごとにトランザクションがロールバックされます。 *** Repository+DBの結合テスト [#f06172db] インメモリではなく、設定ファイルで定義したDBに対してテストを実行したい場合は、以下のようにアノテーションを追記します。 // 公式Docより @DataJpaTest @AutoConfigureTestDatabase(replace=Replace.NONE) class ExampleRepositoryTests { // ... } この時利用するデータベース設定は、application.propetiesから読み込まれます。 src/test/resources -> src/main/resources の順で走査され、src/test以下にapplication.propetiesが存在しない場合はsrc/mainのapplication.propetiesを参照します。 本番・開発用のDBを汚染しないように、テスト用データベース設定の作成を忘れないように注意が必要です。 *** Serviceの単体テスト [#vddb80ad] // 静的インポート(他のインポートは省略) import static org.assertj.core.api.Assertions.*; import static org.mockito.BDDMockito.*; // モックを有効化 @ExtendWith(MockitoExtension.class) public class MyServiceUnitTests { // Repositoryをモックに @Mock private MyRepository repository; // モックRepositoryを注入してServiceを構成 @InjectMocks private MyService service; @Test void testMethod(){ // Serviceで呼び出されるRepositoryメソッドの動作を定義 given(repository.myRepositoryMethod(a)).willReturn(b); // Serviceを呼び出す(内部で上で定義した動作が動く) final returnValue = service.myServiceMethod(a); // 適当なアサーションを行う asertThat(returnValue.getName()).isEqualTo(b); } } Service単体テストでは、テスト対象のService内で使用する他のService、Repositoryクラスをモックにします。 上の例では、@ExtendWith(MockitoExtension.class)でモックBeanの生成を有効化し(Mockito機能)、モックにするクラスに@Mockを、モックを注入したいインスタンスに@InjectMocksを付加することで、モックの生成を行っています。 テストメソッドの中では、 - given().willReturn()の構文でモックメソッドの動作を定義し、 - テスト対象のServiceメソッドを動かし、 - アサーション という手順でテストを行っています。 また、今回はAuto-configurationの必要がなく(モック以外で特にBeanや機能を使用していない)、コンポーネントスキャンも不要なため(@Autowiredを使用していない)、@...Testは使用していません。 必要な機能を付加したい場合は@AutoConfigure...を、自作クラスをAutowiredしたい場合は@Impot(...)を付加します。 もしくは、@...Testを利用してもよいですが、その場合はアノテーション内に@ExtendWith(SpringExtension.class)が含まれており、その内部でモックBeanの生成が有効化されるため、@ExtendWith(MockitoExtension.class)は不要です。 *** Service+Repository+DB結合テスト[#h6a24d85] // 静的インポート(他のインポートは省略) import static org.assertj.core.api.Assertions.*; // クラスアノテーションの説明は下記 /* @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) @AutoConfigureTestEntityManager @Transactional */ @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @Import(MyService.class) public class MyServiceIntegrationTests { // モックではなく全てAutowired @Autowired private TestEntityManager entityManager; @Autowired private MyRepository repository; @Autowired private MyService service; @Test void testMethod(){ // テスト内容 } } Serviceクラス以下の結合テストです。上の例では特にモックは使用していません。 テストの手順の一例は次の通りです。 - TestEntityManagerでDBを初期化 - テスト対象のServiceを動かす - 戻り値等をアサーション - DBの中身をRepositoryで取得してアサーション **** 結合テストクラスアノテーション [#qd3a2eb4] 結合テストのアノテーションは、テストの方針によって付加の仕方が変わってきます。 この話はService+Repository+DBに限らず、部分的な結合テスト全般で応用できると思います。 一つ目は、ほぼ全体をスキャンしてから必要に応じて機能を付加・制限していくスタイルです。 - @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) -- 基本のコンポーネントスキャンを全て行う -- 基本のAuto-configurationを全て行う(一部行わない) -- Webサーバーを非起動にする(サーバーに関しては後述) - @AutoConfigureTestEntityManager -- TestEntityManagerのBeanを生成する - @Transactional -- テストごとにトランザクションをロールバックする 結合の規模が大きい場合は便利ですが、機能の制限が面倒であったり、余分なAuto-configurationによってテスト時間が増える可能性があります。 二つ目は、最大公約数的な@...Testを付加してから必要に応じて機能を付加・制限していくスタイルです。 - @DataJpaTest -- Repositoryをスキャンする -- DataJpaまわりのAuto-configurationを行う -- テストごとにトランザクションをロールバックする -- TestEntityManagerのBeanを生成する -- インメモリDBを利用する(下で打ち消し) - @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -- インメモリDBを利用しない(上の最後の項目の打ち消し) - @Import(MyService.class) -- 必要なServiceをスキャンする 結合の規模が小さい場合や必要な機能が少ない場合は便利ですが、アノテーションが膨大になる可能性があります。 *** Controller単体テスト(MVCテスト) [#f9290307] [[公式Doc:https://docs.spring.io/spring-boot/docs/2.4.3/reference/html/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-autoconfigured-mvc-tests]]を参照。 // 静的インポート import static org.mockito.BDDMockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; // MockMvcの有効化など @WebMvcTest(MyController.class) public class MyControllerUnitTests { @Autowired private MockMvc mvc; @Autowired private ObjectMapper mapper; // Spring Boot版モック生成 @MockBean private MyService service; @Test void testMethod() throws Exception { // MockMvcはExceptionを投げる // モックメソッド定義 given(service.myMethod(a)).willReturn(b); // REST呼び出しとアサーションを同時に行う mvc .perform(post("/this/is/path/") .content(mapper.writeValueAsString(new MyForm(a))) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.result.name").value(b)); } } Controller単体テストでは、MockMvcを用いることでサーバーを立ち上げずにテストが可能です。 @WebMvcTest(...)によって、以下が行われます。 - @Controller(つまり@RestControllerも含む)、@ControllerAdviceなどのスキャン - MockMvcのBean生成 - JacksonのObjectMapperのBean生成 - @MockBeanが使用可能 - Spring Securityの有効化(依存関係があれば) - SpringのValidationの有効化(依存関係があれば) また、引数として渡した単一のControllerに対して、モックBeanの注入が可能になります。 @MockBeanはSpring Boot版のモック生成アノテーションで、@WebMvcTestとの連携等が可能です。 上の例では、Controller内で呼び出すServiceをモックにしています。 MockMvcは、performでAPIを疑似的に叩き、andExpectで戻り値をアサーションできます。 例では、引数としてJSONをボディに持つPOSTを叩き、戻り値のステータスとJSONの個別の値を検証しています。 詳細やGET等の他の方法はここでは省略します。 Spring Securityのテストをしたい方は、自分は当分使う機会がないので検証していませんが、[[こちら:https://docs.spring.io/spring-boot/docs/2.4.3/reference/html/howto.html#howto-use-test-with-spring-security]]を参照してください。うまくいった場合は情報提供お願いします。 Thymeleafも検証可能です .andExpect(view().name("hoge")) .andExpect(model().attribute("name", name)); *** 全体(Controller+Service+Repository+DB)結合テスト [#p1df6f6e] // 静的インポート import static org.assertj.core.api.Assertions.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; // もろもろのクラスアノテーション @SpringBootTest @AutoConfigureMockMvc @AutoConfigureTestEntityManager @Transactional public class PitCoinUserControllerIntegrationTests { // モックなし @Autowired private MockMvc mvc; @Autowired private TestEntityManager entityManager; @Autowired private ObjectMapper mapper; @Autowired private AccountRepository accounts; @Test void testMethod() throws Exception{ // テスト内容 } } はじめに注意として、これは"全体"結合テストですが、サーバーの起動は行っていません。 その理由は以下の通りです。 - サーバーを起動しなくともテスト結果に影響が出なさそうだから - サーバーを起動するとMockMvcが使えなくなり、記法を変更するコストが大きいから -- MockMvcの代替1 : TestWebClient --- 利点 : サーバーの起動・非起動に関わらず使用可能 --- 欠点 : WebFluxとかいう未知技術, RepositoryがFlux対応でないとバグる?(要検証) -- MockMvcの代替2 : TestRestTemplate --- 利点 : RestTemplateと同様に使える --- 欠点 : ''将来的にRestTemplateともども廃止予定'' 全体結合テストの手順の例は以下の通りです。 - TestEntityManagerでDBを初期化する。 - MockMvcで疑似REST呼び出しを行い、アサーションを行う - DBの中身をRepositoryで呼び出し、アサーションを行う ** テストで使用するライブラリの簡単な使い方 [#cd810d25] *** JUnit5 [#w8214240] // テストメソッド @Test void testMethod(){ // テスト内容 } // 各テスト前に実行するメソッド @BeforeEach void beforeEach(){ // DBの初期化等 } class AllTests{ // 入れ子クラスをテスト対象にする @Nested class HalfTests{ @Test void testMethod(){ // テスト内容 } } } // テストクラスで様々な拡張機能を使用可能にする @ExtendWith(...Extension.class) public class TestClass { // テスト } *** AssertJ [#i285d123] // アサーションメソッドを静的インポートする import static org.assertj.core.api.Assertions.*; // aとbが一致するかチェック assertThat(a).isEqualTo(b); // 真偽値をチェック assertThat(a).isTrue(); assertThat(b).isFalse(); // 例外チェック assertThatThrownBy(() -> { // 例外が発生する処理 }). isInstanceOfSatisfying( /* 正しい例外クラス */, (e) ->{ // 発生した例外eが満たすべき条件のアサーションを記述する } ); *** Mockito(BDD) [#k763d9ab] // BDDMockメソッドの静的インポート import static org.mockito.BDDMockito.*; //JUnit5でモックの作成・注入を可能にする @ExtendWith(MockitoExtension.class) public class TestClass{ // モックを使ったテスト } // モック @Mock private SubService sub; // 内部でモックを利用する(=モックを注入される) @InjectMocks private Service service; // 注入先での動作を定義する // service.myMethod(a)が呼ばれるとbを返す given(service.myMethod(a)).willReturn(b); // service.myMethod()の引数にMyClass型が渡されるとcを返す given(service.myMethod(any(MyClass.class))).willReturn(c); ** 今後の積み残し [#m1c4c104] - 外部API呼び出しのモック - サービス間連携でのテスト
タイムスタンプを変更しない
[[SpringBoot]] - 4/5中田 * Spring Boot Test [#i322aa14] Spring Bootでのテストの仕方をまとめ%%たかった%% #contents ** はじめに [#ad97d481] 公式Docを読む時間がある人は[[この項目:https://docs.spring.io/spring-boot/docs/2.4.3/reference/html/spring-boot-features.html#boot-features-testing]]を自分で読むのが一番です。 テストについて学ぶには、Spring Framework、Spring Bootについてのややディープな理解が必須なので、不安な人は[[中田/Techメモ/SpringBoot]]等を参照してください。 PitCoinで実際にテストを書いてみたので、[[こちら:http://teamserv.cs.kobe-u.ac.jp:443/tnakata/pitcointest]]も参考になると思います。 ** Spring Bootテスト全体の話 [#yb02716f] *** テストで使用する技術 [#me700c2d] Spring Bootでテストを行うためには、様々な知識が必要です。 Spring Frameworkのテスト = (バニラJava + 頻出ライブラリ) + (テスト + アサーション) + (DI + モッキング) + Auto-configuration - バニラJava -- 標準のJavaの書き方です。 -- (例:OOP、アノテーション、ラムダ式、JPA) - 頻出ライブラリ -- Javaでよく使われるライブラリです。 -- (例:Jackson) - テスト -- テストの本体です。 -- (例:JUnit5、JUnit4) - アサーション -- テストで満たすべき条件を記述します。 -- (例:AssertJ) - DI -- Bean注入です。 -- (例:@Bean、@Autowired、コンポーネントスキャン) - モッキング -- 注入するBeanをモック(はりぼてクラス)にすることで、クラスを切り離してテストができます。これがDIの主目的です。 -- (例:Mockito) - Auto-configuration -- Springの主機能で、依存性を記述するだけで指定した範囲の機能が有効化され、Beanが構成されます。 *** Bootテストの特徴 [#kc0bd9fb] Spring Bootのテストは、Spring Frameworkのテストをまとめたうえで指向性を持たせたものです。 build.gradleに testImplementation 'org.springframework.boot:spring-boot-starter-test' という依存を追加することで、Spring Frameworkのテストライブラリに加え、以下のような外部ライブラリがいくつかimportして利用できるようになります。 - JUnit 5 : テストライブラリ - AssertJ : アサーションライブラリ - Mockito : モッキングフレームワーク テストの種類に応じてアノテーションを変更することで、コンポーネントスキャンとAuto-configurationの範囲を指定(スライス)することができます。(@DataJpaTest、@WebMvcTestなど) Auto-configurationの内容は、[[@SpringBootTest:https://docs.spring.io/spring-boot/docs/2.4.3/reference/html/appendix-auto-configuration-classes.html#auto-configuration-classes]]、[[スライス:https://docs.spring.io/spring-boot/docs/2.4.3/reference/html/appendix-test-auto-configuration.html]]を参照してください。 @...Testのアノテーションは、 JUnit5の@ExtendWith(SpringExtension.class)を含み、Spring拡張機能が利用可能になります。JUnit4を利用する場合は@RunWith(...)を追加してください。 ** 具体的なテスト手法 [#yb49ccc2] *** Repository単体テスト(Spring Data JPA) [#y6b75efb] [[公式Doc:https://docs.spring.io/spring-boot/docs/2.4.3/reference/html/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-autoconfigured-jpa-test]]を参照。 //公式Docより @DataJpaTest class ExampleRepositoryTests { @Autowired private TestEntityManager entityManager; @Autowired private UserRepository repository; @Test void testExample() throws Exception { this.entityManager.persist(new User("sboot", "1234")); User user = this.repository.findByUsername("sboot"); assertThat(user.getUsername()).isEqualTo("sboot"); assertThat(user.getVin()).isEqualTo("1234"); } } Repository単体テストでは、インメモリのDBを利用します。 そのため、テスト用に依存を追加しておく必要があります。(例:H2を利用する場合は、testImplementation 'com.h2database:h2' をbuild.gradleに追記) @DataJpaTestは、@RepositoryをスキャンしてBeanを自動構成するため、RepositoryのAutowiredが可能です。その他のコンポーネント(@Serviceなど)はデフォルトではスキャンされません。 また、TestEntityManager(標準JavaのJPAにおけるEntityManagerのSpring Boot Testラップ版)のBeanも利用可能なため、Repositoryの機能を使わずにデータベースの初期化が可能になります(perisist()、clear()など)。 @DataJpaTestは@Transactionalを含むアノテーションであるため、テストごとにトランザクションがロールバックされます。 *** Repository+DBの結合テスト [#f06172db] インメモリではなく、設定ファイルで定義したDBに対してテストを実行したい場合は、以下のようにアノテーションを追記します。 // 公式Docより @DataJpaTest @AutoConfigureTestDatabase(replace=Replace.NONE) class ExampleRepositoryTests { // ... } この時利用するデータベース設定は、application.propetiesから読み込まれます。 src/test/resources -> src/main/resources の順で走査され、src/test以下にapplication.propetiesが存在しない場合はsrc/mainのapplication.propetiesを参照します。 本番・開発用のDBを汚染しないように、テスト用データベース設定の作成を忘れないように注意が必要です。 *** Serviceの単体テスト [#vddb80ad] // 静的インポート(他のインポートは省略) import static org.assertj.core.api.Assertions.*; import static org.mockito.BDDMockito.*; // モックを有効化 @ExtendWith(MockitoExtension.class) public class MyServiceUnitTests { // Repositoryをモックに @Mock private MyRepository repository; // モックRepositoryを注入してServiceを構成 @InjectMocks private MyService service; @Test void testMethod(){ // Serviceで呼び出されるRepositoryメソッドの動作を定義 given(repository.myRepositoryMethod(a)).willReturn(b); // Serviceを呼び出す(内部で上で定義した動作が動く) final returnValue = service.myServiceMethod(a); // 適当なアサーションを行う asertThat(returnValue.getName()).isEqualTo(b); } } Service単体テストでは、テスト対象のService内で使用する他のService、Repositoryクラスをモックにします。 上の例では、@ExtendWith(MockitoExtension.class)でモックBeanの生成を有効化し(Mockito機能)、モックにするクラスに@Mockを、モックを注入したいインスタンスに@InjectMocksを付加することで、モックの生成を行っています。 テストメソッドの中では、 - given().willReturn()の構文でモックメソッドの動作を定義し、 - テスト対象のServiceメソッドを動かし、 - アサーション という手順でテストを行っています。 また、今回はAuto-configurationの必要がなく(モック以外で特にBeanや機能を使用していない)、コンポーネントスキャンも不要なため(@Autowiredを使用していない)、@...Testは使用していません。 必要な機能を付加したい場合は@AutoConfigure...を、自作クラスをAutowiredしたい場合は@Impot(...)を付加します。 もしくは、@...Testを利用してもよいですが、その場合はアノテーション内に@ExtendWith(SpringExtension.class)が含まれており、その内部でモックBeanの生成が有効化されるため、@ExtendWith(MockitoExtension.class)は不要です。 *** Service+Repository+DB結合テスト[#h6a24d85] // 静的インポート(他のインポートは省略) import static org.assertj.core.api.Assertions.*; // クラスアノテーションの説明は下記 /* @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) @AutoConfigureTestEntityManager @Transactional */ @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @Import(MyService.class) public class MyServiceIntegrationTests { // モックではなく全てAutowired @Autowired private TestEntityManager entityManager; @Autowired private MyRepository repository; @Autowired private MyService service; @Test void testMethod(){ // テスト内容 } } Serviceクラス以下の結合テストです。上の例では特にモックは使用していません。 テストの手順の一例は次の通りです。 - TestEntityManagerでDBを初期化 - テスト対象のServiceを動かす - 戻り値等をアサーション - DBの中身をRepositoryで取得してアサーション **** 結合テストクラスアノテーション [#qd3a2eb4] 結合テストのアノテーションは、テストの方針によって付加の仕方が変わってきます。 この話はService+Repository+DBに限らず、部分的な結合テスト全般で応用できると思います。 一つ目は、ほぼ全体をスキャンしてから必要に応じて機能を付加・制限していくスタイルです。 - @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) -- 基本のコンポーネントスキャンを全て行う -- 基本のAuto-configurationを全て行う(一部行わない) -- Webサーバーを非起動にする(サーバーに関しては後述) - @AutoConfigureTestEntityManager -- TestEntityManagerのBeanを生成する - @Transactional -- テストごとにトランザクションをロールバックする 結合の規模が大きい場合は便利ですが、機能の制限が面倒であったり、余分なAuto-configurationによってテスト時間が増える可能性があります。 二つ目は、最大公約数的な@...Testを付加してから必要に応じて機能を付加・制限していくスタイルです。 - @DataJpaTest -- Repositoryをスキャンする -- DataJpaまわりのAuto-configurationを行う -- テストごとにトランザクションをロールバックする -- TestEntityManagerのBeanを生成する -- インメモリDBを利用する(下で打ち消し) - @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -- インメモリDBを利用しない(上の最後の項目の打ち消し) - @Import(MyService.class) -- 必要なServiceをスキャンする 結合の規模が小さい場合や必要な機能が少ない場合は便利ですが、アノテーションが膨大になる可能性があります。 *** Controller単体テスト(MVCテスト) [#f9290307] [[公式Doc:https://docs.spring.io/spring-boot/docs/2.4.3/reference/html/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-autoconfigured-mvc-tests]]を参照。 // 静的インポート import static org.mockito.BDDMockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; // MockMvcの有効化など @WebMvcTest(MyController.class) public class MyControllerUnitTests { @Autowired private MockMvc mvc; @Autowired private ObjectMapper mapper; // Spring Boot版モック生成 @MockBean private MyService service; @Test void testMethod() throws Exception { // MockMvcはExceptionを投げる // モックメソッド定義 given(service.myMethod(a)).willReturn(b); // REST呼び出しとアサーションを同時に行う mvc .perform(post("/this/is/path/") .content(mapper.writeValueAsString(new MyForm(a))) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.result.name").value(b)); } } Controller単体テストでは、MockMvcを用いることでサーバーを立ち上げずにテストが可能です。 @WebMvcTest(...)によって、以下が行われます。 - @Controller(つまり@RestControllerも含む)、@ControllerAdviceなどのスキャン - MockMvcのBean生成 - JacksonのObjectMapperのBean生成 - @MockBeanが使用可能 - Spring Securityの有効化(依存関係があれば) - SpringのValidationの有効化(依存関係があれば) また、引数として渡した単一のControllerに対して、モックBeanの注入が可能になります。 @MockBeanはSpring Boot版のモック生成アノテーションで、@WebMvcTestとの連携等が可能です。 上の例では、Controller内で呼び出すServiceをモックにしています。 MockMvcは、performでAPIを疑似的に叩き、andExpectで戻り値をアサーションできます。 例では、引数としてJSONをボディに持つPOSTを叩き、戻り値のステータスとJSONの個別の値を検証しています。 詳細やGET等の他の方法はここでは省略します。 Spring Securityのテストをしたい方は、自分は当分使う機会がないので検証していませんが、[[こちら:https://docs.spring.io/spring-boot/docs/2.4.3/reference/html/howto.html#howto-use-test-with-spring-security]]を参照してください。うまくいった場合は情報提供お願いします。 Thymeleafも検証可能です .andExpect(view().name("hoge")) .andExpect(model().attribute("name", name)); *** 全体(Controller+Service+Repository+DB)結合テスト [#p1df6f6e] // 静的インポート import static org.assertj.core.api.Assertions.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; // もろもろのクラスアノテーション @SpringBootTest @AutoConfigureMockMvc @AutoConfigureTestEntityManager @Transactional public class PitCoinUserControllerIntegrationTests { // モックなし @Autowired private MockMvc mvc; @Autowired private TestEntityManager entityManager; @Autowired private ObjectMapper mapper; @Autowired private AccountRepository accounts; @Test void testMethod() throws Exception{ // テスト内容 } } はじめに注意として、これは"全体"結合テストですが、サーバーの起動は行っていません。 その理由は以下の通りです。 - サーバーを起動しなくともテスト結果に影響が出なさそうだから - サーバーを起動するとMockMvcが使えなくなり、記法を変更するコストが大きいから -- MockMvcの代替1 : TestWebClient --- 利点 : サーバーの起動・非起動に関わらず使用可能 --- 欠点 : WebFluxとかいう未知技術, RepositoryがFlux対応でないとバグる?(要検証) -- MockMvcの代替2 : TestRestTemplate --- 利点 : RestTemplateと同様に使える --- 欠点 : ''将来的にRestTemplateともども廃止予定'' 全体結合テストの手順の例は以下の通りです。 - TestEntityManagerでDBを初期化する。 - MockMvcで疑似REST呼び出しを行い、アサーションを行う - DBの中身をRepositoryで呼び出し、アサーションを行う ** テストで使用するライブラリの簡単な使い方 [#cd810d25] *** JUnit5 [#w8214240] // テストメソッド @Test void testMethod(){ // テスト内容 } // 各テスト前に実行するメソッド @BeforeEach void beforeEach(){ // DBの初期化等 } class AllTests{ // 入れ子クラスをテスト対象にする @Nested class HalfTests{ @Test void testMethod(){ // テスト内容 } } } // テストクラスで様々な拡張機能を使用可能にする @ExtendWith(...Extension.class) public class TestClass { // テスト } *** AssertJ [#i285d123] // アサーションメソッドを静的インポートする import static org.assertj.core.api.Assertions.*; // aとbが一致するかチェック assertThat(a).isEqualTo(b); // 真偽値をチェック assertThat(a).isTrue(); assertThat(b).isFalse(); // 例外チェック assertThatThrownBy(() -> { // 例外が発生する処理 }). isInstanceOfSatisfying( /* 正しい例外クラス */, (e) ->{ // 発生した例外eが満たすべき条件のアサーションを記述する } ); *** Mockito(BDD) [#k763d9ab] // BDDMockメソッドの静的インポート import static org.mockito.BDDMockito.*; //JUnit5でモックの作成・注入を可能にする @ExtendWith(MockitoExtension.class) public class TestClass{ // モックを使ったテスト } // モック @Mock private SubService sub; // 内部でモックを利用する(=モックを注入される) @InjectMocks private Service service; // 注入先での動作を定義する // service.myMethod(a)が呼ばれるとbを返す given(service.myMethod(a)).willReturn(b); // service.myMethod()の引数にMyClass型が渡されるとcを返す given(service.myMethod(any(MyClass.class))).willReturn(c); ** 今後の積み残し [#m1c4c104] - 外部API呼び出しのモック - サービス間連携でのテスト
テキスト整形のルールを表示する