如何使用 junit5 和 testcontainers 测试存储库?

How to test repository with junit5 and testcontainers?

我有一个示例项目,我在其中试验了不同的技术。

我有以下设置:

如何使用 testcontainer-junit5 测试 Repository 层?

我现在的代码示例 CompanyRepositoryTest.java:

@ExtendWith(SpringExtension.class)
@Testcontainers
public class CompanyRepositoryTest {

    @Autowired
    private CompanyRepository companyRepository;

    @Container
    public MySQLContainer mysqlContainer = new MySQLContainer()
            .withDatabaseName("foo")
            .withUsername("foo")
            .withPassword("secret");;

    
    @Test
    public void whenFindByIdExecuted_thenNullReturned()
            throws Exception {
        assertEquals(companyRepository.findById(1L), Optional.ofNullable(null));
    }

    @Test
    public void whenFindAllExecuted_thenEmptyListReturned() {
        assertEquals(companyRepository.findAll(), new ArrayList<>());
    }
}

当我添加 @SpringBootTest 时,我需要设置所有上下文并且有一些应用程序加载上下文问题?

问题是,谁能揭开 @TestContainers 注释的作用?测试存储库时使用它的最佳做法或正确做法是什么?

你说

When I add @SpringBootTest, I need to set up all the context and have some Application load context issues?

如果您想尝试替代方案并且 Testcontainer 不是强制性的,您可以采用不同的方式。

使用SpringBootTest注解不需要加载everyting,可以指定需要的类如

@SpringBootTest(classes = { TheService.class })

或使用@Import注解

并嘲笑其他人,例如

@MockBean
MyService service;

对于数据库连接,您可以使用注释,例如

@ActiveProfiles("my-profile-for-jpa-test")
@DataJpaTest
@EnableJpaAuditing
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)

编辑:我觉得这应该是一条评论,但我想以正确的格式解决问题的 SpringBootTest 部分

  1. 根据docs

The test containers extension finds all fields that are annotated with Container and calls their container lifecycle methods. Containers declared as static fields will be shared between test methods. They will be started only once before any test method is executed and stopped after the last test method has executed. Containers declared as instance fields will be started and stopped for every test method.

因此在您的情况下,它将为每个测试方法重新创建一个容器,它只负责启动和停止容器。如果您需要一些测试数据 - 必须手动完成,因为我看到您有 Flyway,那应该可以。

  1. 你说的是什么“上下文问题”?

  2. 存储库通常不会单独测试,您可以只测试 运行 存储库方法的服务,而不是为两者编写测试。如果你想测试 repos 无论如何 - 用 @Before.

    中的一些数据填充数据库

如果您有更多问题,请提出。

@Testcontainers 注释提供的 JUnit 5 extension 扫描任何使用 @Container 注释声明的容器,然后启动和停止这些容器以进行测试。作为静态字段的容器将与所有测试共享,作为实例字段的容器将为每个测试启动和停止。

如果您使用 Spring 引导,为您的测试设置测试容器的最简单方法可能是在 application-test.yml 中提供属性。这将使用数据源 JDBC URL 来启动 testcontainers 容器。有关详细信息,请参阅测试容器 JDBC support

您还可以使用 @DataJpaTest 而不是 @SpringBootTest:

来仅测试存储库层
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ActiveProfiles("test")
class CompanyRepositoryTest { }

您的 application-test.yml 文件:

spring:
  datasource:
    url: jdbc:tc:mysql:8.0://hostname/databasename
    driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver

在某些情况下,您可能还想改用 @TestPropertySource 注释:

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@TestPropertySource(
    properties = {
        "spring.datasource.url = jdbc:tc:mysql:8.0://hostname/test-database",
        "spring.datasource.driver-class-name = org.testcontainers.jdbc.ContainerDatabaseDriver"
    }
)
class CompanyRepositoryTest { }

请注意 hostnametest-database 实际上并没有在任何地方使用。

这是一个示例,我如何在 Spring 中使用 MySql 配置 Liquibase(与 Flyway 类似的框架):

@DataJpaTest
@TestPropertySource(properties = {"spring.jpa.hibernate.ddl-auto=validate"})
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ContextConfiguration(initializers = { MySqlLiquibaseBaseIT.Initializer.class })
@Testcontainers
public class MySqlLiquibaseBaseIT {

  @Container
  public static MySQLContainer<?> mysql = new MySQLContainer<>(
    DockerImageName
      .parse(MySQLContainer.NAME)
      .withTag("5.7.22"));

  @Configuration
  @EnableJpaRepositories
  @EntityScan
  static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
      TestPropertyValues.of(
        "spring.datasource.url=" + mysql.getJdbcUrl(),
        "spring.datasource.username=" + mysql.getUsername(),
        "spring.datasource.password=" + mysql.getPassword(),
        "spring.datasource.driver-class-name=" + mysql.getDriverClassName())
        .applyTo(configurableApplicationContext.getEnvironment());
    }

    @Bean
    public SpringLiquibase springLiquibase(DataSource dataSource) {
      SpringLiquibase liquibase = new SpringLiquibase();
      liquibase.setDropFirst(true);
      liquibase.setDataSource(dataSource);
      liquibase.setChangeLog("classpath:/db/changelog/db.changelog-master.yml");
      return liquibase;
    }
  }
}

MySqlLiquibaseBaseIT.java