使用 JUnit 和 @DataJpaTest 测试 Spring Data Repository

1、简介

在使用 Spring Data JPA 进行数据持久化的 Spring Boot 应用中,测试与数据库交互的 Repository 至关重要。

本文将带你了解如何使用 Spring Boot 提供的 @DataJpaTest 注解和 JUnit 对 Spring Data JPA Repository 进行有效地测试。

2、了解 @DataJpaTest 和 Repository 类

本节主要介绍在 Spring Boot 应用中,@DataJpaTestRepository 之间的交互。

2.1、@DataJpaTest

@DataJpaTest 注解用于测试 Spring Boot 应用中的 JPA Repository。它是一个专门的测试注解,为测试持久层提供了一个最小的 Spring Context。该注解可与 @RunWith@SpringBootTest 等其他测试注解结合使用。

此外,@DataJpaTest 的范围仅限于应用的 JPA Repository 层。它不会加载整个 Application Context,从而使测试更快、更集中。此注解还为测试 JPA 实体提供了预配置的 EntityManagerTestEntityManager

2.2、Repository

在 Spring Data JPA 中,Repository 是 JPA 实体之上的一个抽象层。它为执行 CRUD(创建、读取、更新、删除)操作和执行自定义查询提供了一组方法。这些 Repository 通常从 JpaRepository 等接口继承而来,负责处理与特定实体类型相关的数据库交互。

3、可选参数

@DataJpaTest 有一些可选参数,可以用来定制测试环境。

3.1、properties

该参数允许指定将应用于测试上下文的 Spring Boot 配置属性。这对于调整数据库连接细节、事务行为或其他与测试需求相关的 application properties 等设置非常有用:

@DataJpaTest(properties = {
    "spring.datasource.url=jdbc:h2:mem:testdb",
    "spring.jpa.hibernate.ddl-auto=create-drop"
})
public class UserRepositoryTest {
    // 测试方法
}

3.2、showSql

这会为测试启用 SQL 日志,可以查看 Repository 方法执行的实际 SQL 查询。此外,这还有助于调试或理解 JPA 查询是如何翻译的。默认情况下,SQL 日志是启用的。可以通过将值设置为 false 关闭它:

@DataJpaTest(showSql = false)
public class UserRepositoryTest {
    // 测试方法
}

3.3、includeFilters 和 excludeFilters

通过这些参数,可以在组件扫描过程中包含或排除特定组件。可以利用它们缩小扫描范围,并通过只关注相关组件来优化测试性能:

@DataJpaTest(includeFilters = @ComponentScan.Filter(
    type = FilterType.ASSIGNABLE_TYPE, 
    classes = UserRepository.class),
  excludeFilters = @ComponentScan.Filter(
    type = FilterType.ASSIGNABLE_TYPE, 
    classes = SomeIrrelevantRepository.class))
public class UserRepositoryTest {
    // 测试方法
}

4、主要功能

在 Spring Boot 应用中测试 JPA Repository 时,@DataJpaTest 注解是一个非常方便的工具。

4.1、配置测试环境

为 JPA Repository 建立适当的测试环境可能既费时又棘手。@DataJpaTest 提供了一个现成的测试环境,其中包括测试 JPA Repository 的基本组件,如 EntityManagerDataSource

该环境专为测试 JPA Repository 而设计。它能确保我们的 Repository 方法在测试事务的上下文中运行,并与 H2 等安全的内存数据库而不是生产数据库进行交互。

4.2、依赖注入

@DataJpaTest 简化了测试类中的依赖注入过程。Repository 和其他基本 Bean 会自动注入测试上下文。这种无缝集成使开发人员能够专注于编写简洁而有效的测试用例,而不必为显式的 Bean 装配而烦恼。

4.3、默认回滚

此外,保持测试的独立性和可靠性也至关重要。默认情况下,每个注解了 @DataJpaTest 的测试方法都会在事务边界内运行。这就确保了在测试结束时,对数据库所做的更改会自动回滚,不会影响到下一次测试。

5、配置

要使用 @DataJpaTest,需要在项目中添加 scopetest(确保它不包含在生产构建中)的 spring-boot-starter-test 依赖。这个轻量级的依赖包含用于测试的 JUnit 等基本测试库。

5.1、在 pom.xml 中添加依赖

pom.xml 中添加以下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-test</artifactId>
    <scope>test</scope>
</dependency>

添加依赖后,就可以在测试中使用 @DataJpaTest 注解了。该注解设置了一个内存 H2 数据库并配置了 Spring Data JPA,使我们可以编写与 Repository 交互的测试。

5.2、创建实体类

创建 User 实体类,代表用户数据:

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    
    // Getter、Setter
}

5.3、创建 Repository 接口

定义 UserRepository,它是 Spring Data JPA Repository 接口,用于管理用户实体:

public interface UserRepository extends JpaRepository<User, Long> {
    // 添加所需的其他方法
}

通过继承 JpaRepository<User, Long>UserRepository 可以访问 Spring Data JPA 开箱即用的标准 CRUD 操作。

此外,还可以在该接口中定义自定义查询方法,以满足特定的数据访问检索需求,如 findByUsername()

public interface UserRepository extends JpaRepository<User, Long> {
    // 通过用户名查找用户的自定义查询方法
    User findByUsername(String username);
}

6、实现 Repository 测试

使用 @DataJpaTest 注解测试应用的 Repository 层。该注解会建立一个内存 H2 数据库,并配置 Spring Data JPA。这样,就可以编写与 Repository 交互的测试了。

6.1、设置测试类

首先,用 @DataJpaTest 注解来设置测试类。该注解会扫描注解为 @Entity 的实体类 和 Spring Data JPA Repository 接口。这样可以确保只加载相关组件进行测试,从而提高测试的针对性和性能:

@DataJpaTest
public class UserRepositoryTest {
    // 定义测试方法
}

要创建 Repository 测试用例,首先需要将要测试的 Repository 注入到测试类中。这可以使用 @Autowired 注解来完成:

@Autowired
private UserRepository userRepository;

6.2、测试生命周期管理

在测试生命周期管理中,@BeforeEach@AfterEach 注解分别用于在每个测试方法之前和之后执行设置和清理操作。这可确保每个测试方法都在干净、隔离的环境中运行,并具有一致的初始条件和清理程序。

将测试生命周期管理纳入测试类的方法如下:

@BeforeEach
public void setUp() {
    // 在每个测试方法之前初始化测试数据
    testUser = new User();
    testUser.setUsername("testuser");
    testUser.setPassword("password");
    userRepository.save(testUser);
}

@AfterEach
public void tearDown() {
    // 在每个测试方法之后删除测试数据
    userRepository.delete(testUser);
}

在使用 @BeforeEach 进行注解的 setUp() 方法中,可以在每个测试方法执行前执行任何必要的设置操作。这可能包括初始化测试数据、设置模拟(Mock)对象或准备测试所需的资源。

相反,在使用 @AfterEach 进行注解的 tearDown() 方法中,可以在每个测试方法执行完毕后执行清理操作。这可能涉及重置测试期间所做的任何更改、释放资源或执行任何必要的清理任务,以将测试环境恢复到初始状态。

6.3、测试插入操作

现在,可以编写与 JPA Repository 交互的测试方法。例如,我们可能想测试能否将新用户保存到数据库中。由于每次测试前都会自动保存用户,因此我们可以直接专注于测试与 JPA Repository 的交互:

@Test
void givenUser_whenSaved_thenCanBeFoundById() {
    User savedUser = userRepository.findById(testUser.getId()).orElse(null);
    assertNotNull(savedUser);
    assertEquals(testUser.getUsername(), savedUser.getUsername());
    assertEquals(testUser.getPassword(), savedUser.getPassword());
}

观察测试用例的控制台日志,会发现以下日志:

Began transaction (1) for test context  
.....

Rolled back transaction for test:  

这些日志表明 @BeforeEach@AfterEach 方法按预期运行。

6.4、测试更新操作

创建一个测试用例来测试更新操作:

@Test
void givenUser_whenUpdated_thenCanBeFoundByIdWithUpdatedData() {
    testUser.setUsername("updatedUsername");
    userRepository.save(testUser);

    User updatedUser = userRepository.findById(testUser.getId()).orElse(null);

    assertNotNull(updatedUser);
    assertEquals("updatedUsername", updatedUser.getUsername());
}

6.5、测试 findByUsername() 方法

测试 findByUsername() 自定义查询方法:

@Test
void givenUser_whenFindByUsernameCalled_thenUserIsFound() {
    User foundUser = userRepository.findByUsername("testuser");

    assertNotNull(foundUser);
    assertEquals("testuser", foundUser.getUsername());
}

7、事务行为

默认情况下,所有用 @DataJpaTest 注解的测试都在事务中执行。这意味着测试期间对数据库所做的任何更改都会在测试结束时回滚,确保数据库保持其原始状态。这种默认行为可防止测试之间的干扰和数据损坏,从而简化测试。

不过,在某些情况下,我们可能需要禁用默认的事务行为来测试某些场景。例如,可能需要在测试结束后保存测试结果。

在这种情况下,可以使用 @Transactional 注解和 propagation = propagation.NOT_SUPPORTED 来覆盖默认的事务回滚行为:

@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public class UserRepositoryIntegrationTest {
    // 测试方法
}

或者,可以禁用单个测试方法的事务:

@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void testMyMethodWithoutTransactions() {
    // 修改数据的操作
}

8、总结

本文介绍了如何使用 @DataJpaTest 在 JUnit 中测试 JPA Repository。

总的来说,@DataJpaTest 是一个强大的注解,可用于测试 Spring Boot 应用中的 JPA Repository。它为测试持久层提供了一个集中的测试环境和预配置工具。通过使用 @DataJpaTest,可以确保 JPA Repository 正常运行,而无需启动整个 Spring Context。


Ref:https://www.baeldung.com/junit-datajpatest-repository