1. 概要

このチュートリアルでは、Spring Bootのフレームワークサポートを使用した書き込みテストについて説明します。単独で実行できる単体テストと、ブートストラップする統合テストSpringについて説明します。テストを実行する前のコンテキスト。

Spring Bootを初めて使用する場合は、 SpringBootの概要を確認してください。

2. プロジェクトの設定

この記事で使用するアプリケーションは、Employeeリソースに対するいくつかの基本的な操作を提供するAPIです。 これは典型的な階層型アーキテクチャです。API呼び出しはControllerからServicePersistenceレイヤーまで処理されます。

3. Mavenの依存関係

まず、テストの依存関係を追加しましょう。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <version>2.5.0</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>test</scope>
</dependency>

spring-boot-starter-test は、テストに必要な要素の大部分を含む主要な依存関係です。

H2DBはインメモリデータベースです。 これにより、テスト目的で実際のデータベースを構成および開始する必要がなくなります。

3.1. JUnit 4

Spring Boot 2.4以降、JUnit5のビンテージエンジンはspring-boot-starter-testから削除されました。 それでもJUnit4を使用してテストを作成する場合は、次のMaven依存関係を追加する必要があります。

<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

4. @SpringBootTestとの統合テスト

名前が示すように、統合テストはアプリケーションのさまざまなレイヤーの統合に焦点を合わせています。 これは、モックが含まれていないことも意味します。

理想的には、統合テストを単体テストから分離し、単体テストと一緒に実行しないようにする必要があります。別のプロファイルを使用して統合テストのみを実行することで、これを実行できます。 これを行う理由はいくつかあります。統合テストには時間がかかり、実行するには実際のデータベースが必要になる場合があります。

ただし、この記事ではこれに焦点を当てず、代わりにメモリ内のH2永続ストレージを利用します。

統合テストでは、テストケースを実行するためにコンテナーを起動する必要があります。 したがって、これにはいくつかの追加のセットアップが必要です—これはすべてSpringBootで簡単です。

@RunWith(SpringRunner.class)
@SpringBootTest(
  SpringBootTest.WebEnvironment.MOCK,
  classes = Application.class)
@AutoConfigureMockMvc
@TestPropertySource(
  locations = "classpath:application-integrationtest.properties")
public class EmployeeRestControllerIntegrationTest {

    @Autowired
    private MockMvc mvc;

    @Autowired
    private EmployeeRepository repository;

    // write test cases here
}

@SpringBootTestアノテーションは、コンテナー全体をブートストラップする必要がある場合に役立ちます。アノテーションは、テストで使用されるApplicationContextを作成することで機能します。

@SpringBootTestwebEnvironment属性を使用して、ランタイム環境を構成できます。 ここではWebEnvironment.MOCKを使用しているため、コンテナは模擬サーブレット環境で動作します。

次に、 @TestPropertySource アノテーションは、テストに固有のプロパティファイルの場所を構成するのに役立ちます。 @TestPropertySource でロードされたプロパティファイルは、既存のapplication.propertiesファイルを上書きすることに注意してください。

application-integrationtest.properties には、永続ストレージを構成するための詳細が含まれています。

spring.datasource.url = jdbc:h2:mem:test
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.H2Dialect

MySQLに対して統合テストを実行する場合は、プロパティファイルで上記の値を変更できます。

統合テストのテストケースは、Controllerレイヤー単体テストのように見える場合があります。

@Test
public void givenEmployees_whenGetEmployees_thenStatus200()
  throws Exception {

    createTestEmployee("bob");

    mvc.perform(get("/api/employees")
      .contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk())
      .andExpect(content()
      .contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
      .andExpect(jsonPath("$[0].name", is("bob")));
}

Controller レイヤーユニットテストとの違いは、ここでは何もモックされておらず、エンドツーエンドのシナリオが実行されることです。

5. @TestConfigurationを使用した構成のテスト

前のセクションで見たように、 @SpringBootTest で注釈が付けられたテストは、完全なアプリケーションコンテキストをブートストラップします。つまり、コンポーネントによって取得されたbeanを@Autowireできます。テストにスキャンイン:

@RunWith(SpringRunner.class)
@SpringBootTest
public class EmployeeServiceImplIntegrationTest {

    @Autowired
    private EmployeeService employeeService;

    // class code ...
}

ただし、実際のアプリケーションコンテキストのブートストラップを避け、特別なテスト構成を使用することもできます。 これは、@TestConfigurationアノテーションを使用して実現できます。 注釈を使用する方法は2つあります。 Beanを@Autowireする同じテストクラスの静的内部クラスのいずれか:

@RunWith(SpringRunner.class)
public class EmployeeServiceImplIntegrationTest {

    @TestConfiguration
    static class EmployeeServiceImplTestContextConfiguration {
        @Bean
        public EmployeeService employeeService() {
            return new EmployeeService() {
                // implement methods
            };
        }
    }

    @Autowired
    private EmployeeService employeeService;
}

または、別のテスト構成クラスを作成することもできます。

@TestConfiguration
public class EmployeeServiceImplTestContextConfiguration {
    
    @Bean
    public EmployeeService employeeService() {
        return new EmployeeService() { 
            // implement methods 
        };
    }
}

@TestConfiguration で注釈が付けられた構成クラスはコンポーネントのスキャンから除外されるため、@Autowireするすべてのテストで明示的にインポートする必要があります。 @Importアノテーションを使用してこれを行うことができます。

@RunWith(SpringRunner.class)
@Import(EmployeeServiceImplTestContextConfiguration.class)
public class EmployeeServiceImplIntegrationTest {

    @Autowired
    private EmployeeService employeeService;

    // remaining class code
}

6. @MockBeanでモックする

サービスレイヤーコードは、リポジトリ:に依存しています

@Service
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Override
    public Employee getEmployeeByName(String name) {
        return employeeRepository.findByName(name);
    }
}

ただし、 Service レイヤーをテストするために、永続レイヤーがどのように実装されているかを知る必要はありません。 理想的には、完全永続層に配線せずに、Service層コードを記述してテストできる必要があります。

これを実現するために、 SpringBootTestが提供するモックサポートを使用できます。

最初にテストクラスのスケルトンを見てみましょう。

@RunWith(SpringRunner.class)
public class EmployeeServiceImplIntegrationTest {

    @TestConfiguration
    static class EmployeeServiceImplTestContextConfiguration {
 
        @Bean
        public EmployeeService employeeService() {
            return new EmployeeServiceImpl();
        }
    }

    @Autowired
    private EmployeeService employeeService;

    @MockBean
    private EmployeeRepository employeeRepository;

    // write test cases here
}

Service クラスを確認するには、 Service クラスのインスタンスを作成し、 @Bean として使用できるようにして、@Autowireを実行できるようにする必要があります。 テストクラスで。 この構成は、@TestConfigurationアノテーションを使用して実現できます。

ここでのもう1つの興味深い点は、@MockBeanの使用です。 は、 EmployeeRepository のモックを作成します。これを使用して、実際のEmployeeRepositoryへの呼び出しをバイパスできます。

@Before
public void setUp() {
    Employee alex = new Employee("alex");

    Mockito.when(employeeRepository.findByName(alex.getName()))
      .thenReturn(alex);
}

セットアップが完了しているため、テストケースはより単純になります。

@Test
public void whenValidName_thenEmployeeShouldBeFound() {
    String name = "alex";
    Employee found = employeeService.getEmployeeByName(name);
 
     assertThat(found.getName())
      .isEqualTo(name);
 }

7. @DataJpaTestとの統合テスト

プロパティとしてidnameを持つEmployee、という名前のエンティティを操作します。

@Entity
@Table(name = "person")
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Size(min = 3, max = 20)
    private String name;

    // standard getters and setters, constructors
}

Spring DataJPAを使用したリポジトリは次のとおりです。

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {

    public Employee findByName(String name);

}

永続性レイヤーコードは以上です。 それでは、テストクラスの作成に取り掛かりましょう。

まず、テストクラスのスケルトンを作成しましょう。

@RunWith(SpringRunner.class)
@DataJpaTest
public class EmployeeRepositoryIntegrationTest {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private EmployeeRepository employeeRepository;

    // write test cases here

}

@RunWith(SpringRunner.class)は、SpringBootテスト機能とJUnitの間のブリッジを提供します。 JUnitテストでSpringBootテスト機能を使用する場合は常に、このアノテーションが必要になります。

@DataJpaTest は、永続性レイヤーのテストに必要ないくつかの標準セットアップを提供します。

  • インメモリデータベースであるH2の構成
  • Hibernate、Spring Data、およびDataSourceの設定
  • @EntityScanを実行します
  • SQLロギングをオンにする

DB操作を実行するには、データベースにすでにいくつかのレコードが必要です。 このデータを設定するには、TestEntityManager。を使用できます。

Spring Boot TestEntityManager は、テストの作成時に一般的に使用されるメソッドを提供する標準のJPA EntityManagerの代替手段です。

EmployeeRepository は、テストするコンポーネントです。

それでは、最初のテストケースを書いてみましょう。

@Test
public void whenFindByName_thenReturnEmployee() {
    // given
    Employee alex = new Employee("alex");
    entityManager.persist(alex);
    entityManager.flush();

    // when
    Employee found = employeeRepository.findByName(alex.getName());

    // then
    assertThat(found.getName())
      .isEqualTo(alex.getName());
}

上記のテストでは、 TestEntityManagerを使用してEmployeeをDBに挿入し、名前による検索APIを介して読み取ります。

assertThat(…)の部分は、SpringBootにバンドルされているAssertjライブラリからのものです。

8. @WebMvcTestを使用した単体テスト

ControllerServiceレイヤーに依存しています。 簡単にするために、1つのメソッドのみを含めましょう。

@RestController
@RequestMapping("/api")
public class EmployeeRestController {

    @Autowired
    private EmployeeService employeeService;

    @GetMapping("/employees")
    public List<Employee> getAllEmployees() {
        return employeeService.getAllEmployees();
    }
}

Controller コードのみに焦点を当てているため、単体テスト用にServiceレイヤーコードをモックするのは自然なことです。

@RunWith(SpringRunner.class)
@WebMvcTest(EmployeeRestController.class)
public class EmployeeRestControllerIntegrationTest {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private EmployeeService service;

    // write test cases here
}

Controllers をテストするには、@WebMvcTestを使用できます。 単体テスト用にSpringMVCインフラストラクチャを自動構成します。

ほとんどの場合、@ WebMvcTest は、単一のコントローラーのブートストラップに制限されます。 @MockBean と一緒に使用して、必要な依存関係のモック実装を提供することもできます。

@WebMvcTest は、 MockMvc も自動構成します。これは、完全なHTTPサーバーを起動せずにMVCコントローラーを簡単にテストする強力な方法を提供します。

そうは言っても、テストケースを書いてみましょう。

@Test
public void givenEmployees_whenGetEmployees_thenReturnJsonArray()
  throws Exception {
    
    Employee alex = new Employee("alex");

    List<Employee> allEmployees = Arrays.asList(alex);

    given(service.getAllEmployees()).willReturn(allEmployees);

    mvc.perform(get("/api/employees")
      .contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk())
      .andExpect(jsonPath("$", hasSize(1)))
      .andExpect(jsonPath("$[0].name", is(alex.getName())));
}

get(…)メソッド呼び出しは、 put() post()などのHTTP動詞に対応する他のメソッドに置き換えることができます。 リクエストではコンテンツタイプも設定していることに注意してください。

MockMvc は柔軟性があり、それを使用して任意のリクエストを作成できます。

9. 自動構成されたテスト

Spring Bootの自動構成されたアノテーションの驚くべき機能の1つは、完全なアプリケーションの一部とコードベースのテスト固有のレイヤーをロードするのに役立つことです。

上記の注釈に加えて、広く使用されているいくつかの注釈のリストを次に示します。

  • @WebF luxTest @WebFluxTest アノテーションを使用して、SpringWebFluxコントローラーをテストできます。 @MockBean と一緒に使用して、必要な依存関係のモック実装を提供することがよくあります。
  • @JdbcTest Weは@JdbcTestアノテーションを使用してJPAアプリケーションをテストできますが、DataSourceのみを必要とするテスト用です。アノテーションは、メモリ内の組み込みデータベースとJdbcTemplate。を構成します。
  • @JooqTest :jOOQ関連のテストをテストするには、DSLContextを構成する@JooqTestアノテーションを使用できます。
  • @DataMongoTest :MongoDBアプリケーションをテストするには、@DataMongoTestが便利なアノテーションです。 デフォルトでは、ドライバーが依存関係を通じて利用できる場合は、メモリー内の組み込みMongoDBを構成し、 MongoTemplateを構成し、 @Document クラスをスキャンし、SpringDataMongoDBリポジトリーを構成します。
  • @DataRedisTest を使用すると、Redisアプリケーションのテストが簡単になります。 @RedisHash クラスをスキャンし、デフォルトでSpringDataRedisリポジトリを構成します。
  • @DataLdapTest は、メモリ内に埋め込まれた LDAP (使用可能な場合)を構成し、 LdapTemplate を構成し、 @Entry クラスをスキャンし、構成します。デフォルトでは、Spring Data LDAPリポジトリ。
  • @RestClientTest :通常、@RestClientTestアノテーションを使用してRESTクライアントをテストします。 Jackson、GSON、Jsonbのサポートなどのさまざまな依存関係を自動構成します。 RestTemplateBuilder; を構成し、デフォルトでMockRestServiceServerのサポートを追加します。
  • @JsonTest :JSONシリアル化のテストに必要なBeanのみを使用してSpringアプリケーションコンテキストを初期化します。

これらの注釈の詳細と統合テストをさらに最適化する方法については、記事 SpringIntegrationTestsの最適化を参照してください。

10. 結論

この記事では、Spring Bootでのテストのサポートについて深く掘り下げ、単体テストを効率的に作成する方法を示しました。

この記事の完全なソースコードは、GitHubにあります。 ソースコードには、さらに多くの例とさまざまなテストケースが含まれています。

また、テストについて学び続けたい場合は、統合テスト Spring統合テストの最適化、および JUnit5の単体テストに関連する個別の記事があります。