Spring 引导:单元测试存储库方法的最佳策略是什么?

Spring Boot: what is the best strategy to unit test repository methods?

我们有很多连接和提取方法,如下所示:

@Query(
        "select new com.company.user.entity.DTO.UserBillingDTO(" +
                    "u.id as id, " +
                    "u.firstName as firstName, " +
                    "u.lastName as lastName, " +
                    "e.tokenId as tokenId," +
                    "u.companyId as companyId," +
                    "e.id as entityId, " +
                    "u.userName as userName, " +
                    "u.locale as locale) " +
                "from User as u "  +
                "join u.profiles as p "  +
                "join p.entity as e "  +
                "where u.id = :userId")
UserBillingDTO findUserForBilling(@Param("userId") String userId);

我想用测试来覆盖这些方法,看看我们的 HQL 查询是否返回了预期的结果。

问题是我如何轻松填充本地数据库以测试我们方法的结果?

你能想到别的吗?我如何编写易于团队遵循的可维护测试套件?

编辑:

如果您没有使用特定于数据库的功能,例如自定义SQL 特定于Oracle 的功能,您可以在@SpringBootTest 测试中设置嵌入式数据库。使用 @AutoConfigureTestDatabase 注释对测试 class 进行注释,例如用嵌入式内存数据库替换默认应用程序 DataSource bean:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2)

您可以根据 81.5 Use a Higher-level Database Migration Tool 章节使用 Flyway 或 Liquibase 填充数据库。根据文档:

You can also use Flyway to provide data for specific scenarios. For example, you can place test-specific migrations in src/test/resources and they are run only when your application starts for testing. Also, you can use profile-specific configuration to customize spring.flyway.locations so that certain migrations run only when a particular profile is active. For example, in application-dev.properties, you might specify the following setting:

使用存储库填充

一种可能性是在您的测试中创建记录。这就是您所说的显而易见的解决方案。对于您当前的代码,您可以这样做:

@Test
public void findUserForBilling() {
    repository.saveAll(Lists.newArrayList(
        new User("1", "John", "Doe", "JDoe123", new Profile(..., new ProfileEntity(1, "token123"))),
        new User("2", "Jane", "Doe", "TheJane", new Profile(..., new ProfileEntity(2, "token234")))));
    UserBillingDTO dto = repository.findUserForBilling("1");
    assertThat(dto.getId()).isEqualTo("1");
    // ...
}

虽然在某些情况下您的测试数据可能会占用一些地方,但在这种情况下,它只有几行,这与通常的单元测试 preparation/given 场景一样。

Be aware: In this type of test, you are not testing your entity mapping. If there's an issue within your entity mapping, you won't be able to tell using these tests.

使用 SQL 文件填充

另一种可能性是使用单独的 SQL 文件,例如 user-dataset.sqlsrc/test/resources:

insert into user (id, firstname, lastname) values ("1", "John", "Doe");
insert into user (id, firstname, lastname) values ("2", "Jane", "Doe");
--- ...

然后您可以使用 @Sql 注释将该数据集包含在您的测试中,例如:

@RunWith(SpringRunner.class)
@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Sql("classpath:user-dataset.sql") // Add this
public class UserRepositoryTest {
    // ...
}

您可以将 @Sql 注释添加到您的测试 class,甚至添加到单个测试方法,例如:

@Test
@Sql("classpath:user-dataset.sql") // Add this
public void findUserForBilling() {
    UserBillingDTO dto = repository.findUserForBilling("1");
    assertThat(dto.getId()).isEqualTo("1");
    // ...
}

Be aware: If I'm not mistaken, Spring will create the datasource once for the test class. If you execute your dataset for each test method, you'll have to add a delete statement to delete all existing records first.

如果您只需要一个数据集来进行所有测试,您甚至可以进一步简化并命名您的数据集 data.sql。如果您使用的是内存数据库(这可能对您的测试有用),因为 Spring 引导会自动在您的 class 路径上执行一个 data.sql 文件,您甚至不需要需要 @Sql 注释。

使用 DbUnit 填充

另一种方法是选择像 DbUnit, which allows you to define your dataset in an XML format. You can use it in combination with Spring Test DBUnit 这样的框架,以便更轻松地与 Spring 集成。

你必须记住,它不像使用 @Sql 那样容易设置,你需要了解一种额外的语言,即用于设置数据集的 XML 结构。