使用Testcontainers测试Spring Boot


Testcontainers是一个 Java 库,可在Docker容器内启动服务、运行测试并最终销毁容器。您无需担心任何事情,框架可以完成这项工作。只要确保你已经安装了 Docker,然后你就可以开始了。该库支持数十种不同的数据库和模块(PostgreSQL、MySQL、MongoDB、Kafka、Elasticsearch、Nginx、Toxiproxy等)。即使您没有找到您需要的那个,您也可以使用通用容器创建。
第一步是添加所需的依赖项:

testImplementation 'org.testcontainers:junit-jupiter' 
testImplementation 'org.testcontainers:postgresql' 
runtimeOnly 'org.postgresql:postgresql'

然后我们需要在src/test/resources配置文件配置.这样Spring能够通过区分不同的配置文件的。配置文件名称如application-PROFILE_NAME.yml. 例如,application-test-containers.yml仅当test-containers配置文件处于活动状态时才应用命名的文件。

spring:
  datasource:
    url: jdbc:tc:postgresql:9.6.8:///test_database
    username: user
    password: password
  jpa:
    hibernate:
      ddl-auto: create

JDBC 连接字符串中的后缀是啥?这就是JUnit 5和测试容器的结合所带来的魔力。问题是您根本不需要任何编程配置!当框架看到url包含tc后缀时,它会在内部运行所有必要的 Docker 命令。您可以在此处找到更多示例。
我们设置spring.jpa.hibernate.ddl-auto=create属性,以便根据实体类的定义自动创建数据库模式。
现在让我们PersonCreateService.createFamily看一下方法及其 H2 测试。
@Service
@RequiredArgsConstructor
public class PersonCreateServiceImpl implements PersonCreateService {

  private final PersonValidateService personValidateService;
  private final PersonRepository personRepository;

  @Override
  @Transactional
  public List<PersonDTO> createFamily(Iterable<String> firstNames, String lastName) {
    final var people = new ArrayList<PersonDTO>();
    firstNames.forEach(firstName -> people.add(createPerson(firstName, lastName)));
    return people;
  }
  
  @Override
  @Transactional
  public PersonDTO createPerson(String firstName, String lastName) {
    personValidateService.checkUserCreation(firstName, lastName);
    final var createdPerson = personRepository.saveAndFlush(
        new Person()
            .setFirstName(firstName)
            .setLastName(lastName)
    );
    return DTOConverters.toPersonDTO(createdPerson);
  }
}
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
@AutoConfigureTestDatabase
class PersonCreateServiceImplSpringBootTest {

  @Autowired
  private PersonRepository personRepository;
  @MockBean
  private PersonValidateService personValidateService;
  @Autowired
  private PersonCreateService personCreateService;

  @BeforeEach
  void init() {
    personRepository.deleteAll();
  }

  @Test
  void shouldCreateOnePerson() {
    final var people = personCreateService.createFamily(
        List.of("Simon"),
       
"Kirekov"
    );
    assertEquals(1, people.size());
    final var person = people.get(0);
    assertEquals(
"Simon", person.getFirstName());
    assertEquals(
"Kirekov", person.getLastName());
    assertTrue(person.getDateCreated().isBefore(ZonedDateTime.now()));
  }

  @Test
  void shouldRollbackIfAnyUserIsNotValidated() {
    doThrow(new ValidationFailedException(
""))
        .when(personValidateService)
        .checkUserCreation(
"John", "Brown");
    assertThrows(ValidationFailedException.class, () -> personCreateService.createFamily(
        List.of(
"Matilda", "Vasya", "John"),
       
"Brown"
    ));
    assertEquals(0, personRepository.count());
  }
}

我们如何使用 Testcontainers PostgreSQL实例运行测试?我们所要做的就是添加两个注释。@AutoConfigureTestDatabase虽然应该删除。

  • @ActiveProfiles("test-containers")— 激活test-containers配置文件,以便 Spring 可以读取前面描述的配置文件
  • @Testcontainers— 告诉在Docker 中自动运行 PostgreSQL 实例

@SpringBootTest(webEnvironment = WebEnvironment.NONE)
@Testcontainers
@ActiveProfiles("test-containers")
class PersonCreateServiceImplTestContainers {

  @Autowired
  private PersonRepository personRepository;
  @MockBean
  private PersonValidateService personValidateService;
  @Autowired
  private PersonCreateService personCreateService;

  @BeforeEach
  void init() {
    personRepository.deleteAll();
  }

  @Test
  void shouldCreateOnePerson() {
    final var people = personCreateService.createFamily(
        List.of(
"Simon"),
       
"Kirekov"
    );
    assertEquals(1, people.size());
    final var person = people.get(0);
    assertEquals(
"Simon", person.getFirstName());
    assertEquals(
"Kirekov", person.getLastName());
    assertTrue(person.getDateCreated().isBefore(ZonedDateTime.now()));
  }

  @Test
  void shouldRollbackIfAnyUserIsNotValidated() {
    doThrow(new ValidationFailedException(
""))
        .when(personValidateService)
        .checkUserCreation(
"John", "Brown");
    assertThrows(ValidationFailedException.class, () -> personCreateService.createFamily(
        List.of(
"Matilda", "Vasya", "John"),
       
"Brown"
    ));
    assertEquals(0, personRepository.count());
  }
}

运行测试成功。
 
存储库测试
测试存储库层怎么样?让我们再看看 H2 的例子。
@DataJpaTest
class PersonRepositoryDataJpaTest {

  @Autowired
  private PersonRepository personRepository;

  @Test
  void shouldReturnAlLastNames() {
    personRepository.saveAndFlush(new Person().setFirstName("John").setLastName("Brown"));
    personRepository.saveAndFlush(new Person().setFirstName(
"Kyle").setLastName("Green"));
    personRepository.saveAndFlush(new Person().setFirstName(
"Paul").setLastName("Brown"));

    assertEquals(Set.of(
"Brown", "Green"), personRepository.findAllLastNames());
  }
}

规则相同,但略有不同。@DataJpaTest是类似前面@AutoConfigureTestDatabase的独特注释。默认情况下,此注释用 H2 实例替换任何数据源。因此,我们需要通过添加replace=Replace.NONE属性来覆盖此行为。
@DataJpaTest
@Testcontainers
@ActiveProfiles("test-containers")
@AutoConfigureTestDatabase(replace = Replace.NONE)
class PersonRepositoryTestContainers {

  @Autowired
  private PersonRepository personRepository;

  @Test
  void shouldReturnAlLastNames() {
    personRepository.saveAndFlush(new Person().setFirstName(
"John").setLastName("Brown"));
    personRepository.saveAndFlush(new Person().setFirstName(
"Kyle").setLastName("Green"));
    personRepository.saveAndFlush(new Person().setFirstName(
"Paul").setLastName("Brown"));

    assertEquals(Set.of(
"Brown", "Green"), personRepository.findAllLastNames());
  }
}

点击标题看 javarevisited原文