gpt4 book ai didi

java - 如何将 Testcontainers 与 @DataJpaTest 结合起来避免代码重复?

转载 作者:行者123 更新时间:2023-12-04 13:07:48 34 4
gpt4 key购买 nike

我想将测试容器与 @DataJpaTest 一起使用(和 @SpringBootTest )使用 JUnit 5。我使用 @Testcontainers 进行基本设置工作和 @Container像这样的注释:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import static org.assertj.core.api.Assertions.assertThat;

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Testcontainers
public class AtleteRepositoryTest {
@Container
private static final PostgreSQLContainer<?> CONTAINER = new PostgreSQLContainer<>("postgres:11");

@DynamicPropertySource
static void registerProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", CONTAINER::getJdbcUrl);
registry.add("spring.datasource.username", CONTAINER::getUsername);
registry.add("spring.datasource.password", CONTAINER::getPassword);
}

@Autowired
private AtleteRepository repository;

@Test
void testSave() {
repository.save(new Atlete("Wout Van Aert", 0, 1, 0));

assertThat(repository.count()).isEqualTo(1);
}
}
https://github.com/wimdeblauwe/blog-example-code/tree/feature/testcontainers-datajpatest/testcontainers-datajpatest对于完整的示例代码( AtleteRepositoryTestTeamRepositoryTestTestcontainersDatajpatestApplicationTests )。
为了避免重复声明 PostgreSQL 容器和动态属性,我尝试了以下操作:
JUnit 5 扩展
Baeldung 有一个关于如何做的博客 use a JUnit 5 extension to avoid the duplication .
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.testcontainers.containers.PostgreSQLContainer;

public class PostgreSQLExtension implements BeforeAllCallback, AfterAllCallback {

private PostgreSQLContainer<?> postgres;

@Override
public void beforeAll(ExtensionContext context) {
postgres = new PostgreSQLContainer<>("postgres:11");

postgres.start();
System.setProperty("spring.datasource.url", postgres.getJdbcUrl());
System.setProperty("spring.datasource.username", postgres.getUsername());
System.setProperty("spring.datasource.password", postgres.getPassword());
}

@Override
public void afterAll(ExtensionContext context) {
postgres.stop();
}
}
如果您只有 1 个测试,它会起作用,但如果您同时运行多个测试(使用 IntelliJ 或使用 Maven)则不起作用。在这种情况下,其中一项测试将失败,因为无法建立与数据库的连接。
另请注意,此扩展不使用 DynamicPropertyRegistry ,但是普通的环境变量。
feature/testcontainers-datajpatest_baeldung-extension代码的分支。
使用公共(public)父类(super class)
分行上 feature/testcontainers-datajpatest_database-base-test ,我尝试使用一个通用的父类(super class):

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;

public class DatabaseBaseTest {
private static final PostgreSQLContainer<?> CONTAINER = new PostgreSQLContainer<>("postgres:11");

@BeforeAll
static void start() {
CONTAINER.start();
}

@AfterAll
static void stop() {
CONTAINER.stop();
}

@DynamicPropertySource
static void registerProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", () -> {
String jdbcUrl = CONTAINER.getJdbcUrl();
System.out.println("jdbcUrl = " + jdbcUrl);
return jdbcUrl;
});
registry.add("spring.datasource.username", CONTAINER::getUsername);
registry.add("spring.datasource.password", CONTAINER::getPassword);
}
}
不幸的是,这也不起作用。我在日志中注意到 @DynamicPropertySource带注释的方法只被调用一次,而不是每次测试,这让我尝试了选项 3:
@DynamicPropertySource 共同的父类(super class)在子类中
使用通用父类(super class)时,但添加了 @DynamicPropertySource每个子类中的方法,它再次起作用。
这样一个子类的示例代码:
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class AtleteRepositoryTest extends DatabaseBaseTest {

@DynamicPropertySource
static void registerProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", () -> {
String jdbcUrl = CONTAINER.getJdbcUrl();
System.out.println("jdbcUrl = " + jdbcUrl);
return jdbcUrl;
});
registry.add("spring.datasource.username", CONTAINER::getUsername);
registry.add("spring.datasource.password", CONTAINER::getPassword);
}

@Autowired
private AtleteRepository repository;

@Test
void testSave() {
repository.save(new Atlete("Wout Van Aert", 0, 1, 0));

assertThat(repository.count()).isEqualTo(1);
}
}
见分行 feature/testcontainers-datajpatest_database-base-test_subclasses对于那个版本。
所以虽然它有效,但每个测试类中仍然有很多重复。
有没有其他选择可以避免重复?

最佳答案

为了避免 Testcontainers 代码重复,我通常遵循 2 种方法:

  • 使用 ApplicationContextInitializer @ContextConfiguration

  • import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.test.util.TestPropertyValues;
    import org.springframework.context.ApplicationContextInitializer;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.testcontainers.containers.PostgreSQLContainer;

    @Slf4j
    public class PostgreSQLContainerInitializer
    implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    private static PostgreSQLContainer sqlContainer = new PostgreSQLContainer("postgres:10.7");

    static {

    sqlContainer.start();
    }

    public void initialize (ConfigurableApplicationContext configurableApplicationContext){
    TestPropertyValues.of(
    "spring.datasource.url=" + sqlContainer.getJdbcUrl(),
    "spring.datasource.username=" + sqlContainer.getUsername(),
    "spring.datasource.password=" + sqlContainer.getPassword()
    ).applyTo(configurableApplicationContext.getEnvironment());
    }

    }
    import com.sivalabs.myservice.common.PostgreSQLContainerInitializer;
    import com.sivalabs.myservice.entities.User;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
    import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
    import org.springframework.test.context.ContextConfiguration;
    import javax.persistence.EntityManager;
    import java.util.Optional;
    import static org.assertj.core.api.Assertions.assertThat;

    @DataJpaTest
    @AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
    @ContextConfiguration(initializers = {PostgreSQLContainerInitializer.class})
    class UserRepositoryTest {

    @Autowired
    EntityManager entityManager;

    @Autowired
    private UserRepository userRepository;

    @Test
    void shouldReturnUserGivenValidCredentials() {
    User user = new User(null, "test@gmail.com", "test", "Test");
    entityManager.persist(user);

    Optional<User> userOptional = userRepository.login("test@gmail.com", "test");

    assertThat(userOptional).isNotEmpty();
    }
    }
  • 使用 @DynamicPropertySource Java 8+ 接口(interface)

  • import org.springframework.test.context.DynamicPropertyRegistry;
    import org.springframework.test.context.DynamicPropertySource;
    import org.testcontainers.containers.PostgreSQLContainer;
    import org.testcontainers.junit.jupiter.Container;
    import org.testcontainers.junit.jupiter.Testcontainers;

    @Testcontainers
    public interface PostgreSQLContainerInitializer {

    @Container
    PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:12.3");

    @DynamicPropertySource
    static void registerPgProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.datasource.url", postgres::getJdbcUrl);
    registry.add("spring.datasource.username", postgres::getUsername);
    registry.add("spring.datasource.password", postgres::getPassword);
    }
    }
    @DataJpaTest
    @AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
    class UserRepositoryTest implements PostgreSQLContainerInitializer {

    ....
    ....
    }
    使用这些方法,我们不必重复 PostgreSQL容器声明和 Spring 属性设置。
    是否使用 PostgreSQL容器作为 静态 字段与否取决于您是要为每个测试启动一个新容器还是每个测试类启动一个容器。
    PS:
    我避免使用通用基类方法,因为有时一个测试只需要 1 个容器,而另一个测试需要多个容器。如果我们按照在公共(public)基类中添加所有容器,那么对于每个测试/类,所有这些容器都将启动,而不管它们的使用情况如何,这使得测试非常缓慢。

    关于java - 如何将 Testcontainers 与 @DataJpaTest 结合起来避免代码重复?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68602204/

    34 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com